SOLID – Principle that Elevate your Level
- 2024/7/10
- ブログ
- 2024, coding skill, development, principle, SOLID, ブログ
- SOLID – Principle that Elevate your Level はコメントを受け付けていません
この記事の目次
1. Single Responsibility Principle (SRP)
Principle: A class should have only one job or responsibility, which means that each class should have only one reason to change.
Example: Imagine we are building an employee management system. Initially, we had an employee class that managed employee details, calculated salary, and saved data in the database.
public class Employee { private String name; private String id; public Employee(String name, String id) { this.name = name; this.id = id; } public void calculateSalary() { // Calculate employee salary } public void saveToDatabase() { // Save employee information to the database } }
Correct: According to SRP, we should separate these tasks into different classes.
public class Employee { private String name; private String id; public Employee(String name, String id) { this.name = name; this.id = id; } // Getters and setters } public class SalaryCalculator { public void calculateSalary(Employee employee) { // Calculate employee salary } } public class EmployeeRepository { public void saveToDatabase(Employee employee) { // Save employee information to the database } }
2. Open/Closed Principle (OCP)
Principle: A module should be open for extension but closed for modification, which means that you should be able to add new features without changing existing code.
Example: In a payment system, imagine that we have a PaymentProcessor class that handles credit card payments.
public class PaymentProcessor { public void processCreditCardPayment() { // Process credit card payment } }
Correct:
If we want to add a new payment method like PayPal, we should not change the PaymentProcessor. Instead, we should extend the system using interfaces and new classes.
public interface PaymentMethod { void processPayment(); } public class CreditCardPayment implements PaymentMethod { public void processPayment() { // Process credit card payment } } public class PayPalPayment implements PaymentMethod { public void processPayment() { // Process PayPal payment } } public class PaymentProcessor { private PaymentMethod paymentMethod; public PaymentProcessor(PaymentMethod paymentMethod) { this.paymentMethod = paymentMethod; } public void process() { paymentMethod.processPayment(); } }
3. Liskov Substitution Principle (LSP)
Objects of a parent class should be replaceable with objects of a child class without breaking the program.
Example: Suppose we have a Bird class with a fly() method, and a subclass Penguin that inherits from Bird.
public class Bird { public void fly() { System.out.println("Bird is flying"); } } public class Penguin extends Bird { @Override public void fly() { throw new UnsupportedOperationException("Penguins can't fly"); } }
Correct: Penguins cannot fly, so this breaks the LSP rule. We should redesign by separating birds into flying and non-flying categories.
public interface FlyingBird { void fly(); } public interface NonFlyingBird { void walk(); } public class Sparrow implements FlyingBird { @Override public void fly() { System.out.println("Sparrow is flying"); } } public class Penguin implements NonFlyingBird { @Override public void walk() { System.out.println("Penguin is walking"); } }
4. Interface Segregation Principle (ISP)
This principle tells that clients should not be forced to depend on interfaces they do not use.
Example: In a banking system, instead of having a single Account interface with many methods, we should split it into smaller interfaces.
public interface Account { void deposit(); void withdraw(); void transfer(); }
Correct: Instead, split it into smaller interfaces:
public interface DepositAccount { void deposit(); } public interface WithdrawAccount { void withdraw(); } public interface TransferAccount { void transfer(); }
So, Classes that need specific functionalities only implement the corresponding interfaces like above
5. Dependency Inversion Principle (DIP)
Then finally, High-level modules should not depend on low-level modules. Both should depend on abstractions.
Example: Instead of having the OrderProcessor class directly create an instance of EmailNotification, we should use an interface INotificationService.
public class EmailNotification { public void sendNotification() { // Send email notification } } public class OrderProcessor { private EmailNotification emailNotification; public OrderProcessor() { this.emailNotification = new EmailNotification(); } public void processOrder() { // Process order emailNotification.sendNotification(); } }
Correct: According to DIP, we use an interface to separate the dependency:
public interface INotificationService { void sendNotification(); } public class EmailNotification implements INotificationService { @Override public void sendNotification() { // Send email notification } } public class SMSNotification implements INotificationService { @Override public void sendNotification() { // Send SMS notification } } public class OrderProcessor { private INotificationService notificationService; public OrderProcessor(INotificationService notificationService) { this.notificationService = notificationService; } public void processOrder() { // Process order notificationService.sendNotification(); } }
6. Importance of SOLID
Using the SOLID principles helps developers write code that is easier to read and maintain. It also makes it easier to add new features without breaking existing code.
By applying this, we are able to build a software system that can meet the continuously changing requirements, and evolve separately, making it easier to fix bugs and add new features. But the more important thing is to acknowledge these principles. Doing so not only enhances our coding skills but also helps us develop more professional and clean systems.
The Relationship Between Software Architecture and Source Code
Creating Serverless Functions Using CloudFormation: A Step-by-Step Guide
カテゴリー: