-
Notifications
You must be signed in to change notification settings - Fork 0
The SOLID Design Principles π¨βπ
Lyes S edited this page Jun 25, 2022
·
9 revisions
Table Of Contents
- Single Responsibility Principle
- Open-Closed Design Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Ref: Martin, Robert C. (2000). "Design Principles and Design Patterns"
- Each class should be responsible for a single part or functionality of the system.
public class Entity {
public void printTooMuchDetails() {}
public void calculateEstimate() {}
public void addEntityToDatabase() {}
}
- The Entity class has three separate responsibilities: reporting, calculation, and database. This violate the SRP principle.
- Separate the above class into three classes with separate responsibilities.
- A module should be open for extension but closed for modification
public class Pizza {
public double getPrice (Pizza pizza) {
if (pizza instanceof VegetarianPizza) {
return pizza.getPrice() * 0.9;
}
if (pizza instanceof MeatFeastPizza) {
return pizza.getPrice() * 0.5;
}
}
}
- Adding another subclass implies the modification of Pizza class by adding another if statement to getPrice method. This violate the Open-Closed principle.
- Override getPrice() in each subclass that extends Pizza.
- Subclasses should be substitutable for their base classes.
public class Bird {
void fly();
}
public class Eagle extends Bird {
@Override
void fly() {
// TODO
}
}
public class Ostrich extends Bird {
@Override
void fly() {
throw new UnsupportedOperationException("Ostrich can't fly.");
}
}
- Ostrich class can't implement fly method since Ostrich doesn't fly. This violate Liskov Substitution principle.
-
Introduce an intermediate layer class 'FlyingBird' then :
- FlyingBird class extends Bird class
- Move fly method from Bird class to FlyingBird class
- Eagle extends FlyingBird class
- Ostrich extends Bird class
- Many client specific interfaces are better than one general purpose interface.
interface OrderService {
void orderBurger(int quantity);
void orderFries(int fries);
}
class BurgerOrderService implements OrderService {
private static Logger logger = LoggerFactory.getLogger(BurgerOrderService.class);
@Override
public void orderBurger(int quantity) {
logger.info("Received order of {} burgers", quantity);
}
@Override
public void orderFries(int fries) {
throw new UnsupportedOperationException("No fries in burger order");
}
}
class FriesOrderService implements OrderService {
private static Logger logger = LoggerFactory.getLogger(FriesOrderService.class);
@Override
public void orderBurger(int quantity) {
throw new UnsupportedOperationException("No burger in fries order");
}
@Override
public void orderFries(int fries) {
logger.info("Received order of {} fries", fries);
}
}
- It does not make sense for a BurgerOrderService class to implement the orderFries method as a BurgerOrderService does not have any fries. This violate the Interface Segregation and the Single Responsibility principles.
- Segregate OrderService interface into multiple small cohesive interfaces so that no class is forced to implement any interface.
- Depend upon Abstractions. Do not depend upon concretions.
public class File {
private static Logger logger = LoggerFactory.getLogger(File.class);
private String fileName;
public File(String fileName) {
this.fileName = fileName;
}
public void encrypt(AESEncrypter aesEncrypter) {
logger.info("{} to {}", aesEncrypter.encryptFile(), fileName);
}
}
public class AESEncrypter {
@Override
public String encryptFile() {
return "Applying AES Encryption";
}
}
- Unable to use another encrypter type such as 'RSAEncrypter' without refactoring the class File.
- Create an interface 'Encrypter' which will be implemented by 'AESEncrypter' and 'RSAEncrypter' and make sure that the class 'File' will depend on 'Encrypter' as abstraction instead of implementations (AESEncrypter and RSAEncrypter).
Β© 2024 | Lyes Sefiane All Rights Reserved | CC BY-NC-ND 4.0