What does SOLID stand for?
SOLID is a mnemonic acronym of 5 acronyms themselves: SRP – Single Responsibility Principle, OCP – Open-Closed Principle, LSP – Liskov Substitution Principle, ISP – Interface Segregation Principle, DIP – Dependency Inversion Principle.
These ideas were first mentioned by Robert C. Martin (Uncle Bob) in his paper “Design Principles and Design Patterns“. Later, Michael C. Feathers coined the SOLID acronym, which was then re-used by Uncle Bob in the chapter “Design Principles” in his book “Clean Architecture” (Affiliate Link).
Single Responsibility Principle (SRP)
There should never be more than one reason for a class to changeSRP: The Single Responsibility Principle – Robert C. Martin
The single responsibility principle was coined by Robert C. Martin himself but he also pays credit to Tom DeMarco’s concept of cohesion, which he described in “Structured Analysis and System Specification” (Affiliate Link). It is the first of the SOLID principles and encourages to give classes only a single and definite reason to change. If you can think of more than a single reason for the class to change, the class has more than one responsibility. SRP is often confused with the “Do one thing”-rule At a first glance, this concept seems rather abstract and useless, but the example that is provided in the initial paper is kind of helpful:
Imagine a rectangle class that has the two public methods draw and area. The draw method should return coordinates or any graphical representation. The area method returns the area of the current rectangle instance. The rectangle class thereby has two responsibilities. It’s responsible for calculating the area and responsible to draw itself.
If you are confused by the types behind the arguments in the constructor, you should have a look at Python type hints. However, changes in the rectangle class would affect the GUI as well as the geometric calculation app. Changes in the rectangle class could potentially break the whole system or lead to unpredictable bugs. The solution Martin proposes in this specific case is to split the class into two separate ones:
This separation leads to a single responsibility for each the geometric rectangle and the rectangle class. Changes in the draw method now can no longer affect the way they are calculated.
The attentive readers might have noticed that this concept’s strict and mindless usage will also lead to poor software design. It would help if you never used the single responsibility theory without proper support of other constructs like modules or facades. Otherwise, the structure proposed by the SRP will cause the code to break into a thousand confusing pieces
Open-Closed Principle (OCP)
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.The Open Closed Principle – Robert C. Martin
Whatever your personal definition of stable code is, this SOLID principle should be part of it. A good software architecture will almost certainly always fulfill the open-closed rule. Originally, Bertrand Mayer coined this concept in his book “Object-Oriented Software Construction” (Affiliate Link). OCP applies to all kinds of objects in programming you can imagine. This philosophy’s main focus is to allow you to scale your classes and modules without caring about legacy code. To simplify this, we are just going to look at applying it to classes.
The key message of the open-closed principle, in this case, is that your classes should be open for extension but closed for modification. Meaning, once you created your class, it shouldn’t change any more. However, it could change by simply creating a child class that thereby extends its behavior. Imagine you had a user class that holds a name and an age.
Now imagine you want to extend this class by an attribute that saves the user’s favorite game. A naive solution to this problem would be to simply add an attribute “favorite_game” to the user class:
This might work if your system is small or in development. But if you want to change this in a productive system, things are going to break. Not only did the signature change because the constructor now expects a favorite_game, but also the __repr__ method changed and might break things further. This violates Meyer’s postulate. A possible solution to this again could be inheritance:
By using this, your functionality is extended, but you don’t apply any changes to the original class. Please note: Inheritance isn’t always a good solution or a solution at all, but it is the easiest example to make. SOLID is not directly related to inheritance or polymorphism.
Liskov Substitution Principle (LSP)
Subtype Requirement: Let φ(x) be a property provable about objects x of type T. Then φ(y) should be true for objects y of type S where S is a subtype of T.A behavioral notion of subtyping – Barbara Liskov and Jeanette Wing
When you are most familiar with Python or hate maths, the Liskov substitution principle might be slightly confusing. This is because it originally was designed for statically typed programming languages like Java, C, or FORTRAN. Barbara Liskov introduced it in 1987. It is often considered the hardest of the SOLID principles to understand. Visualizing Liskov’s definition leads to the following class diagram:
We have a type T and a subtype S, and objects of x that are of type T and objects y of type S. Also, all the elements poss an attribute φ. A representation in Python code could look like this:
And this indeed fulfills the Liskov substitution principle. Any object of type S could replace its parent class. How could you possibly violate this?
If you look closely, you will see that the subtype S implements its own class attribute phi of type string instead of list. This violates Liskov’s theory. You can no longer replace T with objects of type S. One becomes aware of this concept’s meaningfulness when you think about implementing a method “print_phis” that should solely print all elements in the class attribute phi. Either instances of class T or S would run into a runtime error or lead to a higher degree of complexity in the “print_phis” method due to additional conditionals.
Interface Segregation Principle (ISP)
Clients should not be forced to be depend upon interfaces that they do not use.The Interface Segregation Principle – Robert C. Martin
ISP or interface segregation principle is the SOLID equivalent of high cohesion in GRASP (General Responsibility Assignment Software Principles). Thereby it naturally supports loose coupling and maintainability. The main message behind ISP is that large Interfaces should be split into multiple ones. Functions and classes should not depend on methods they don’t use. Look at the following example for a “fat” interface:
Please excuse the far-fetched example, nothing I’m proud of. However, as you can see, the GeometricInterface has two abstract methods of which “get_diameter()” is not used by its subtype square. Thereby the interface segregation rule is violated. There are many possible solutions to this. We could, for instance, segregate the GeometricInterface into three Interfaces:
Dependency Inversion Principle (DIP)
A: High level modules should not depend upon low level Modules. Both should depend upon abstractions.
B: Abstractions should not depend upon details. Details should depend upon abstractions.The Dependency Inversion Principle – Robert C. Martin
In my opinion, the dependency inversion principle is the easiest one to understand. However, it can be the hardest to implement. In essence, the DIP states that modules should not rely on modules that belong to a subordinate concept and should not rely on generalizations. A quick piece of code that is often referenced:
The DIP violation here is that a switch is a concept that is logically in a layer above the light bulb, and the switch relies on it. This will lead to poor extensibility or even circular imports that prevent the program from being interpreted or compiled.
Instead of the light bulb telling the switch how the bulb should be handled, the switch should tell the light bulb how to implement it. The naive approach would be to define an interface that tells the light bulb how it should behave to be used with a switch.
Visualized as a class diagram, this source code would lead to the object-oriented design:
The dependency has been inverted. Instead of the switch relying on the light bulb, the light bulb now relies on an interface in a higher module. Also, both rely on abstractions, as required by the DIP. Last but not least, we also fulfilled the requirement “Abstractions should not depend upon details. Details should depend upon abstractions” – The details of how the device behaves rely on the abstraction (Device interface).
As with all guidelines, it’s not recommended to follow the SOLID principles blindly either. Principles, patterns, and guidelines can easily turn into anti-patterns and lead to new problems. Sometimes it not preventable to violation some of these. Our job as a developer is to create code and programs that are easy to read and thereby easy to maintain and extend. Robustness and correctness are more or less nice side effects of this approach. These are great guidelines, but that’s what they are, guidelines. Specific problems need specific solutions. All principles and patterns can offer are suggestions and best practices.
If you support my statement or are of a completely different opinion I would love to hear about it in the comments section.
- SRP: The Single Responsibility Principle – objectmentor.com, 02.02.2015: https://web.archive.org/web/20150202200348/http://www.objectmentor.com/resources/articles/srp.pdf
- The Open-Closed Principle – objectmentor.com, 02.02.2015: https://web.archive.org/web/20150212141727/http://www.objectmentor.com:80/resources/articles/ocp.pdf
- The Liskov Substitution Principle – objectmentor.com 11.02.2015: https://web.archive.org/web/20150211000751/http://www.objectmentor.com/resources/articles/lsp.pdf
- The Interface Segregation Principle – objectmentor.com 10.02.2015: https://web.archive.org/web/20150210224002/http://www.objectmentor.com/resources/articles/isp.pdf
- The Dependency Inversion Principle: objectmentor.com 10.02.2015: https://web.archive.org/web/20150210213530/http://www.objectmentor.com/resources/articles/dip.pdf
- Design Principles and Design Patterns: https://cds.cern.ch/record/1419478/files/0135974445_TOC.pdf
- Barbara Liskov, Professor at MIT: http://www.pmg.csail.mit.edu/~liskov/
- GRASP – General Responsibility Assignment Software Principles: https://code-specialist.com/code-principles/grasp/
- KISS – https://code-specialist.com/code-principles/kiss-principle/
- Do one thing – https://code-specialist.com/code-principles/do-one-thing/
- Python type hints – https://code-specialist.com/python/type-hints/
I am a developer and entrepreneur from Germany. I chose studying computer science because I love building things. I am of the opinion that there isn’t one truth (especially for computer science concepts) and whoever claims so isn’t that trustworthy in my eyes. I constantly try to improve things and myself. In my spare time I support businesses by offering them most of my services for free. Beside that I am kind of addict to e-learning courses.
Book Recommendations on Clean Code (Affiliate)