Clean Architecture Notes - SOLID (Part 2)
You could follow the previous post here
To build a building, we need bricks. On the one hand, if the bricks aren’t well made, the architecture of the building doesn’t matter much. On the other hand, you can make a substantial mess with well-made bricks.
II. Design Principles
Good software systems begin with clean code. The SOLID principles tell us how to arrange our functions and data structures into classes, and how those classes should be interconnected
Or you could say:
The SOLID principles tell us how to arrange the bricks into walls and rooms
The goal of the principles is the creation of mid-level software structures (module level) that:
• Tolerate change
• Are easy to understand
• Are the basis of components that can be used in many software systems
1. Single Responsibility Principle
- A module should be responsible to one, and only one actor
=> So that each one has only one reason to change
- SRP is about functions and classes
- At the level of components, it becomes Common Closure Principle
- At the architectural level, it becomes the Axis of Change
2. Open-Closed Principle
- Software classes, modules should be open for extension, but closed for modification
- Higher-level components in that hierarchy are protected from the changes made to lower-level components
The goal is to make the system easy to extend without incurring a high impact of change. This goal is accomplished by partitioning the system into components, and arranging those components into a dependency hierarchy that protects higher-level components from changes in lower-level components.
3. Liskov Substitution Principle
- Objects of a superclass shall be replaceable with objects of its subclasses without breaking the application.
- The LSP can, and should, be extended to the level of architecture.
4. Interface Segregation Principle
- It is harmful to depend on modules that contain more than you need.
=> if there is any change on the module that you depend on, could lead to recompiled and redeployed, even though nothing that it cared about has actually changed.
Ex: In Java, we have 2 classes which implement 1 interface. And each class need only one method from that interface.
The design violated the ISP so the source code of User 1 will inadvertently depend on op2 and op3, even though it doesn’t call them.
=> segregating your big interface into smaller and more specific ones.
5. Dependency Inversion Principle
The Dependency Inversion Principle (DIP) tells us that the most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions.
- Stable Abstractions: interfaces are less volatile than implementations
- Good architects work hard to reduce the volatility of interfaces, try to find ways to add functionality to implementations without making changes to the interfaces.
- Don’t refer to volatile concrete classes, but refer to abstract interfaces instead.
Don’t derive from volatile concrete classes
Don’t override concrete functions:
Concrete functions often require source code dependencies. When you override those functions, you do not eliminate those dependencies—indeed, you inherit them. To manage those dependencies, you should make the function abstract and create multiple implementations.
=> should create an interface and implement it.
- Never mention the name of anything concrete and volatile
And how to deal with that? We could use Abstract Factory pattern to deal with the creation of volatile concrete objects.
Ex: The Application uses the ConcreteImpl through the Service interface. However, the Application must somehow create instances of the ConcreteImpl.
To achieve this without creating a source code dependency on the ConcreteImpl, the Application calls the makeSvc method of the ServiceFactory interface. This method is implemented by the ServiceFactoryImpl class, which derives from ServiceFactory. That implementation instantiates the ConcreteImpl and returns it as a Service.
You could see that the curve line separates the abstract from the concrete. And the important thing is:
All source code dependencies cross that curved line pointing in the same direction, toward the abstract side
And the source code dependencies are inverted against the flow of control - which is why we refer to this principle as Dependency Inversion.
(to be continued)