SOLID are five basic principles for object-oriented programming and design. If applied, the solution is more likely to be easy to maintain and extend over time, which helps to create good software architecture. The SOLID acronym stands for:
S - Single responsibility principle
O - Open closed principle
L - Liskov substitution principle
I - Interface segregation principle
D - Dependency inversion principle
Single responsability principle (SRP)
What is wrong with the above code? Should we really have all the battery logic inside the start method? and the ignition process? No, we shouldn’t, it has multiple responsability.
Single responsabolity principle says that a class should only have one responsability. So the solution is moving the logic to the Starter and Battery classes and let the start method just start the engine.
Open-closed principle (OCP)
Following the same approach let’s see what’s wrong with this code:
If in the future we want to add more customer types, we will need to modify the class adding more ifs. This class should be closed for modification open for extension. We fix the code crating a derived class SilverCustomer that will override the discount method. And when we get another Customer type? another derived class!:
Liskov substitution principle (LSP)
Derived classes should be perfectly substitutable for their base classes.
In the above code an Orange cannot replace an Apple so the code doesn’t follow the Liskov principle. How can we fix it? Creating a generic base class for both fruits.
Interface segregation principle (ISP)
The goal behind ISP is that no client consuming an interface should be forced to depend on methods it does not use. For example, you might have a class that implements an interface called IPersist.
Now if we update IPersist to include the two new methods we have in ReservationLog class, it might be useful for more implementations of IPersist.
Well…that was a mistake, now we are forcing all the classes that implement IPersist to implement methods they are not using. We are clearly breaking the interface segregation principle. In fact, IPersist is not just persisting (saving) now is also logging.
To fix it is really easy, we need a new interface to separate responsabilities. And then only the classes that need to log will implement the new interface.
Dependency inversion principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions.
If we follow the Dependency inversion principle we will be able to build loosely coupled classes.
Let’s see an example that violates the principle:
Notice high level modules call low level modules and instantiate their dependencies as they need them. Here ProductService calls ProductRepository, but before startintg it needs to create a new one up using the ‘new’ keyword. Two dependencies are created in the constructor with the ‘new’ keyword. This breaks the Single Responsibility Principle as the class is forced to carry out work that’s not really its concern.
The ProductService is thus tightly coupled to those two concrete classes. Obtaining the correct pricing and data store strategy should not be the responsibility of the ProductService. And whenever those strategies change you must update the ProductService class. It is also difficult to test ProductService in isolation.
In order to remove the hard dependency we will inject in the constructor new abstractions that we will create for the discount and for the repository.
Now anyone using Product Service will know they need an implementation of IProductRepository and IProductStrategy, but not an specific one.
I hope the examples I picked were good enough to understand the SOLID principles. This was just an introduction, there is much more detail available.