This small post is about the SOLID principles. They have been described in the books of Uncle Bob and in his blog, which I invite you to follow cleancoders.com. But as I know them, I wanted to put them in my blog.
SOLID is an acronym of their combined names :
- SRP: Single Responsibility Principle
- OCP: Open/Closed Principle
- LSP: Liskov Substitution Principle
- ISP: Interface Segregation Principle
- DIP: Dependency Inversion Principle
With SRP, every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. Wikipedia Here's an example. Say you have to implement an application to manage a database of meal ideas so you can plan what you will eat during the next week. The application will have to remember meal recipes, to remember what you plan to eat and to build a report of what you will have to buy at the grocery. Sure, you could create a Meal class, that have two responsibility. That is, it could remember its ingredients list to build its part of the shopping list and it could know the preparation steps to cook the meal. However, such a class would be required to change for two reasons. First, if the format of the shopping list ever changes, you would have to modify the way the ingredients list are returned. Second, if the way preparation steps are used changes. If you want to respect the single responsibility principle, these two separate responsibilities should be in two separate classes. For example, you would then have a meal class that holds the ingredients and a recipe class that holds the preparation steps. Then, each class will be focused on a single concern and this will make every classes smaller, more robust and much easier to test.
With OCP, software entities should be open for extension, but closed for modification. In other word, we should develop classes such as if we ever need to add a functionnality, we should create a class that inherits the behavior of the first class (through abstraction mainly) instead of modifying the initial behavior. Furthermore, we should use abstract base classes in order to avoid modification of existing behaviour. Let's take back the example above. You have a list of ingredient for a meal but you want to know how much calories you will ingest with that meal. You have found a huge database of ingredients that gives the number of calories for each ingredient. You would then try to create a class that read the name of an ingredient and analyse it to find key words needed to get data from the database (of course, the search will be done by another class, in order to follow SRP). For example, say you have the ingredient "2 pounds of red potatoes". Your class would have to search the string to find the quantity (2), the measuring unit (pounds), and the ingredient (red potatoes). That class would be huge, first, and if there is different format of ingredients or new formats of ingredients that are added, you would have to modify the class so much that the bad smell would be unbearable. However, if you want to follow OCP, a solution is to create a chain of responsibility to analyse the string. Then, the module would be open to extension (add a link to the chain to treat a particular set of exceptional formatting) but closed to modification (the existing chain links remain untouched).
With LSP, we should code in a way that if we replace dynamically a class by one of the class' children, the behavior of the program should not be affected and correctness should be maintained. I honestly don't have a personal example with that principle that I can remember, since I do not tend to violate it very often. But the example in Wikipedia is a good one.
With ISP, we should split interfaces which are large into smaller and more specific ones so that when a class uses an interface, it can only see what it is supposed to use. In other words, no class should depend on methods it does not use. Here's an example: I once worked with a guy in a project with Microsoft Azure. We needed to use an older system of distant modules. In order to do that, the guy had build a facade|proxy class with 2000+ lines in it. The class was overriding some methods of the older system, adding functionnalities to the older system and using combination of functionnalities. If we don't mind about this huge unsustainable and untested monster class, I still have to say that this class was implementing an interface used everywhere in the guy's code. We could see everywhere small classes that imported that monster class only to use one or two methods. Within weeks, the technical debth grew so much that the guy was taking a whole day only to find out how he would add a certain functionnality without making everything explode. It was definitely NOT my code, but I strongly suggested to this guy to follow the ISP by spliting the interface of his abomination into small ones so that his code we be a bit cleaner. I've also suggested that he split his big class into smaller ones (SRP), but he did not like the idea for whatever reason(he's an "old school" programmer a bit).
With DIP, two principles are stated. First, "high-level modules should not depend on low-level modules. Both should depend on abstractions." Second, "abstractions should not depend upon details. Details should depend upon abstractions". This principle can be observed, for example, through the Adapter pattern. We can also use interfaces so that high level classes and low level classes never depend of each other, thus reducing the coupling of the code. This principle is a bit harder to follow at first, but it will allow to greatly reduce coupling between class.
Of course, the S.O.L.I.D. principles are not the only principles to remember. When I'll have more time, I will add another post about the famous and infamous Demeter's law. I will also discuss a bit of flaws about some design patterns of the Gang of Four.