The SOLID Principles of Object-Oriented Orogramming

Photo by Guillaume Meurice on Unsplash
Photo by Guillaume Meurice on Unsplash
SOLID is an object-oriented programming principle promoted by Robert C. Martin. Design smells are caused by the violation of one of more SOLID principles. Learning SOLID helps developers eliminate the smells from their designs, making them as clean, simple, and expressive as possible.

SOLID is an object-oriented programming principle promoted by Robert C. Martin. Design smells are caused by the violation of one of more SOLID principles. Learning SOLID helps developers eliminate the smells from their designs, making them as clean, simple, and expressive as possible.

Design Smells – The Odors of Rotting Software

The software is rotting when it starts to exhibit any of the following odors.

Rigidity – The design is hard to change

The system is hard to change because every change forces many other changes to other parts of the system.

Fragility – The design is easy to break

Changes cause the system to break in places that have no conceptual relationship to the part that was changed.

Immobility – The design is hard to reuse

It is hard to disentangle the system into components that can be reused in other systems.

Viscosity – It is hard to do the right thing

Doing thing right is harder than doing things wrong.

When faced with a change, developers find more than one way to make the change. Some of the ways preserve the design; other do not (they are hacks). When the design-preserving methods are harder to employ than the hacks, the viscosity of the design is high.

Needless Complexity – Overdesign

The design contains infrastructure that adds no direct benefit.

It frequently happens when developers anticipate changes to the requirements, and put facilities in the software to deal with those potential changes.

Needless Repetition – Mouse abuse

The design contains repeating structures that could be unified under a single abstraction.

It frequently happens when developers copy a part of code and paste it into other places.

Opacity – Disorganized expression

It is hard to read and understand. It does not express its intent well.

SRP (The Single-Responsibility Principle)

A module should be responsible to one, and only one, actor.

The word “cohesive” implies the SRP. Cohesion is the force that binds together the code responsible to a single actor.

The Employee class in the figure below contains three methods:

  • calculatePay(): specified by the accounting department, they report to the CFO.
  • reportHours(): specified by HR, they report to the COO.
  • save(): specified by DBA, they report to the CTO.

The developer put these three methods into the Employee class, so the Employee class is responsible for the three actors. This violates the SRP principles.

Suppose calculatePay() and reportHours() share the method regularHours() for calculating non-overtime working hours. Now suppose the CFO team wants to adjust the calculation of non-overtime working hours, but the COO team does not. The developer started to modify calculatePay(), and found that regularHours() was used to calculate non-overtime working hours, so he or she modified regularHours(). But the developer didn’t notice that reportHours() also uses regularHours().

After the modification was complete, the CFO team tested the system to confirm that the new functionality worked. But the COO team was completely unaware that the method of calculating non-overtime hours had been adjusted. Eventually, this will cost the company.

The Employee class, from Clean Architecture.
The Employee class, from Clean Architecture.

The solution is to move these three methods to individual classes. Each class is responsible for only one role – follow the SRP principle. The shared Employee Data is a simple data structure with no methods.

The three classes do not know about each other, from Clean Architecture.
The three classes do not know about each other, from Clean Architecture.

OCP (The Open-Closed Principle)

A software artifact should be open for extension but closed for modification.

The OCP guides us the design of classes and modules. 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.

How can it be possible to “change the behavior of a module without changing its source code”? The answer is abstraction.

The design in the figure below conform with the OCP principle. Suppose there is only ScreenPresenter at the beginning. We want to extend the functionality so that it can output to PDF. We only need to implement PrintPresenter without any modification to FinancialReportController. Presenter interface or abstract class is defined in FinancialReportController. ScreenPresenter and PrintPresenter will implement or inherit Presenter to extend the function, and do not change FinancialReportController.

The component relationships are unidirectional, from Clean Architecture.
The component relationships are unidirectional, from Clean Architecture.

LSP (The Liskov Substitution Principle)

Subtypes must be substitutable for their base types.

The figure below conforms with the LSP principle, because Billing does not depend on any subtype of License. These two subtypes (PersonalLicense and BusinessLicense) can replace the License type.

License, and its derivatives, conform to LSP, from Clean Architecture.
License, and its derivatives, conform to LSP, from Clean Architecture.

The diagram below violates the LSP principle because User must depend on the Square type.

The infamous square/rectangle problem, from Clean Architecture.
The infamous square/rectangle problem, from Clean Architecture.

The following code, in User.draw(), it must know the type of rectangle before deciding which method to call.

class User {
    private void draw(Rectangle rectangle) {
        if (rectangle instanceof Square) {
            ((Square) rectangle).setSide(10);
        } else {
            rectangle.setWidth(10);
            rectangle.setHeight(10);
        }
    }
}

ISP (The Interface-Segregation Principle)

Clients should not be forced to depend on methods that they do not use.

In the figure below, the UI interface contains four methods. DepositTransaction only needs requestDepostAmount(), but it must implement the other three methods. These three methods of DepositTransaction will not have any code. When the name or parameters of requestWithdrawalAmount() are changed, DepositTransaction should also be modified accordingly. This is very strange, because DepositTransaction does not use requestWithdrawalAmount(), and when it changes, DepositTransaction has to be modified accordingly.

ATM Transaction Hierarchy, from Agile Software Development.
ATM Transaction Hierarchy, from Agile Software Development.

The solution is to use the LSP principle to segregate the UI interface into several interfaces, as shown in the figure below.

Segregated ATM UI Interface, from Agile Software Development.
Segregated ATM UI Interface, from Agile Software Development.

DIP (The Dependency-Inversion Principle)

a. High-level modules should not depend on low-level modules. Both should depend on abstractions.
b. Abstractions should not depend on detail. Details should depend on abstractions.

The diagram below shows the traditional development approach. The high-level Policy Layer depends on the low-level Mechanism Layer, which in turn depends on the lower-level Utility Layer. Dependency is transitive, so Policy Layer transitively depends on Utility Layer.

Naive layering scheme, from Agile Software Development.
Naive layering scheme, from Agile Software Development.

The figure below is a more appropriate model, which conforms with the DIP principle. Each higher layer declares an abstract interface for the services it needs, and the lower layers implement these abstract interfaces. This way higher layers do not depend on lower layers. The lower layers instead depend on the abstract interfaces in the higher layers. This also removes the dependency of Policy Layer on Utility Layer.

Inverted layers, from Agile Software Development.
Inverted layers, from Agile Software Development.

Conclusion

It is highly recommended to further read Robert C. Martin’s Agile Principles, Patters, and Practices in C# and Clean Architecture. In addition to discussing SOLID in detail, these books also contains many topics, such as agile design, UML, design patters, architectures and so on. For a developer, these are important knowledge.

Leave a Reply

Your email address will not be published. Required fields are marked *