SOLID Design Principles
- Summary
-
Discussion
- What problems are solved or avoided by applying SOLID?
- As a beginner, what are some things to keep in mind when applying SOLID?
- Could you explain the Single Responsibility Principle?
- Could you explain the Open-Closed Principle?
- Could you explain the Liskov Substitution Principle?
- Could you explain the Interface Segregation Principle?
- Could you explain the Dependency Inversion Principle (DIP)?
- What are some common criticisms of SOLID?
- Milestones
- References
- Further Reading
- Article Stats
- Cite As
In the world of object-oriented programming (OOP), there are many design guidelines, patterns or principles. Five of these principles are usually grouped together and are known by the acronym SOLID. While each of these five principles describes something specific, they overlap as well such that adopting one of them implies or leads to adopting another.
Programming in strict conformance to SOLID is generally not expected. However, programmers must be aware of SOLID and use them depending on the context.
In general, SOLID helps us manage code complexity. It leads to more maintainable and extensible code. Even with big change requests, it's easier to update the code.
Discussion
-
What problems are solved or avoided by applying SOLID? Software may start with a clean and elegant design but over time they become hard to maintain, often requiring costly redesigns. Robert Martin, who's credited with writing down the SOLID principles, points out some symptoms of rotting design due to improperly managed dependencies across modules:
- Rigidity: Implementing even a small change is difficult since it's likely to translate into a cascade of changes.
- Fragility: Any change tends to break the software in many places, even in areas not conceptually related to the change.
- Immobility: We're unable to reuse modules from other projects or within the same project because those modules have lots of dependencies.
- Viscosity: When code changes are needed, developers will prefer the easier route even if they break existing design.
Antipatterns and improper understanding of design principles can lead to STUPID code: Singleton, Tight Coupling, Untestability, Premature Optimization, Indescriptive Naming, and Duplication. SOLID can help developers stay clear of these.
The essence of SOLID is managing dependencies. This is done via interfaces and abstractions. Modules and classes should not be tightly coupled.
-
As a beginner, what are some things to keep in mind when applying SOLID? SOLID is really a guideline and not a rule. They work in many cases but they're not guaranteed to always work.
Just knowing the principles will not turn a bad programmer into a good one. This means that programmers need to understand why these principles make sense. They need to apply them with judgement. If they see code that violates these principles, they must try to see if the violations can be justified. If not, they can apply one or more principles to improve the code. Practice is the key.
-
Could you explain the Single Responsibility Principle? A class or a module must have a specific responsibility and nothing more. Put it another way, it should change for only one reason. We can say that the responsibility is encapsulated within the class and there's stronger cohesion within the class.
For example, an Automobile class can start or stop itself but the task of washing it belongs to the CarWash class. In another example, a Book class has properties to store its own name, author and text. But the task of printing the book must belong to the BookPrinter class. The BookPrinter class might print to console or another medium but such dependencies are removed from the Book class.
When SRP is followed, testing is easier. With a single responsibility, the class will have fewer test cases. Less functionality also means less dependencies to other modules or classes. It leads to better code organization since smaller and well-purposed classes are easier to search.
-
Could you explain the Open-Closed Principle? It should be possible to extend a module with additional behaviour without modifying it. This means that functions or base class methods should not get polluted with details of subclasses. A function that checks object types is a violation of OCP. This is because when a new object type is added, this function has to be modified to handle the new type. Abstraction is the key to getting OCP right. Two possible ways of doing this is polymorphism or templates/generics.
OCP is important since classes may come to us via third-party libraries. We should be able to extend those classes without worrying if those base classes can support our extensions. But inheritance can lead to subclasses depending on base class implementation. To avoid this, use of interfaces is recommended. This additional abstraction leads to loose coupling.
For example, a Car class might have methods AccelerateAudi, AccelerateBMW, and so on. This is a violation of OCP. Instead, we should have an ICar interface with method Accelerate. Each car subclass can implement this interface.
-
Could you explain the Liskov Substitution Principle? This principle states that we can substitute a subclass for its base class without affecting behaviour. This avoids misusing inheritance. It helps us conform to the "is-a" relationship. We can also say that subclasses must fulfil a contract defined by the base class. In this sense, it's related to Design by Contract that was first described by Bertrand Meyer. For example, it's tempting to say that a circle is a type of ellipse but circles don't have two foci or major/minor axes.
In another example, TRubberDucky is not exactly a duck because it can swim or quack but can't fly. So if a wildlife simulator instantiates a rubber duck and tries to make it fly, it will encounter an error. Thus, TRubberDucky as a subclass of TDuck is a violation of LSP.
LSP is also related to Duck Typing. In fact, duck typing doesn't even require a type hierarchy via inheritance.
-
Could you explain the Interface Segregation Principle? Segregate the interfaces so that subclasses are not required to use all of them. In other words, many client-specific interfaces are better than one general-purpose interface.
For example, a single logging interface for writing and reading logs is useful for a database but not for a console. Reading logs make no sense for a console logger.
Another example is an IMatrixOperations interface that has two methods for inverse and transpose operations. Problem is that for a matrix that's not regular, inverse operation is invalid. Such a matrix based on this interface would throw an exception for an inverse operation. Applying ISP, we would break this interface into separate ones, IRegularMatrixOperations (with inverse operation) and IMatrixOperations (that adds transpose operation). IMatrixOperations derives from IRegularMatrixOperations.
-
Could you explain the Dependency Inversion Principle (DIP)? DIP says that modules should depend upon interfaces or abstract classes, not concrete classes. It's an inversion because implementations depend upon abstractions and not the other way round. Instead of high-level modules depending on low-level modules, let's decouple them and make use of abstractions.
Let's say we instantiate a Windows98Machine object that's automatically created with a StandardKeyboard. Problem is that now we've coupled this keyboard with the machine. It's difficult to instantiate another machine with a different keyboard. This tight coupling also makes it difficult to test the Windows98Machine class. Instead, keyboard must be an interface. Any keyboard variant that uses this interface can be passed into the machine when the latter is constructed.
-
What are some common criticisms of SOLID? While some criticisms are valid, often they arise due to misunderstanding of SOLID. People have called the principles vague. They lead to complex and unintelligible code since we end up with many interfaces and many small classes. DIP depends on dependency injection frameworks. LSP requires implementation inheritance. Most of these are not really true. SOLID principles are condensed software wisdom. Just knowing them is not a substitute for experience and understanding.
SOLID focuses too much on dependencies. It encourages use of abstractions resulting in codebase littered with interfaces. To manage this problem, we end up introducing IoC containers and mocking frameworks, making the code more difficult to understand. SOLID produces testable code with low coupling but code is unintelligible.
It's been said that OCP doesn't work in practice, particularly for domain entities or business classes that by nature change frequently. Following OCP would lead to long inheritance chains and also a violation of another design principle, "favour composition over inheritance". People have different interpretations about what should be open and what should be closed. This leads to inconsistencies.
Milestones
1987
Barbara Liskov of MIT presents at a conference a paper titled Data Abstraction and Hierarchy. She uses the term "substitution property" and explains,
Data abstractions provide the same benefits as procedures, but for data. Recall that the main idea is to separate what an abstraction is from how it is implemented so that implementations of the same abstraction can be substituted freely.
With growing codebases and complexity, object-oriented software design becomes popular to better manage software. But this change of programming paradigm from procedural to object-oriented does not automatically lead to clean code. Developers write large classes and methods. Code is duplicated. Old habits continue. There arises a need to guide developers design object-oriented software the right way.
1995
Robert Martin publishes the book Agile Software Development: Principles, Patterns, and Practices. He explains in detail all the five SOLID principles under a section named "Agile Design". Thus, SOLID becomes an essential aspect of Agile methodology. Sometime later Michael Feathers coins the term SOLID as a useful way to remember the principles.
References
- Ahmed Khan, Ovais Mehboob. 2018. "C# 7 and .NET Core 2.0 High Performance." Packt Publishing, April. Accessed 2019-05-23.
- AlignMinds. 2015. "How to write SOLID Code – Tips for Beginners." AlignMinds, September 04. Accessed 2019-05-23.
- Cecconi, Marco. 2014. "Say "No" to the Open/Closed pattern." Sklivvz, June 08. Accessed 2019-05-23.
- Durand, William. 2013. "From STUPID to SOLID Code!" July 30. Accessed 2019-05-23.
- Geihsler, Brian. 2014. "Why I Don't Teach SOLID." Blog, Quality is Speed, August 22. Accessed 2019-05-23.
- Hikri, Kobi. 2018. "SOLID Software Design Principles and How Fit in a Microservices Architecture Design." Pluralsight, December 15. Accessed 2019-05-23.
- InterVenture. 2017. "Principles of OOD." InterVenture, May 12. Accessed 2020-03-26.
- Janssen, Thorben. 2018. "SOLID Design Principles Explained: The Open/Closed Principle with Code Examples." Stackify, March 28. Accessed 2019-05-23.
- Jayakanth R. 2018. "Why should every Magento developer follow SOLID Principles?" Blog, DCKAP, June 08. Accessed 2019-05-23.
- Karam, Lea Maya. 2017. "The 5 S.O.L.I.D. Principles Explained." DZone, July 25. Accessed 2019-05-23.
- Liskov, Barbara. 1987. "Data Abstraction and Hierarchy." OOPSLA '87 Addendum to the Proceedings, pp. 17-34, October. Accessed 2019-05-23.
- Liskov, Barbara H. and Jeannette M. Wing. 1994. "A Behavioral Notion of Subtyping." ACM Transactions m Programming Languages and Systems, vol. 16, no. 6, pp. 1811-1841, November 1994. Accessed 2019-05-23.
- Marston, Tony. 2011. "Not-so-SOLID OO Principles." June 08. Updated 2016-11-01. Accessed 2019-05-23.
- Martelli, Alex. 2000. "polymorphism (was Re: Type checking in python?)." comp.lang.python, Google Groups, July 26. Accessed 2019-05-23.
- Martin, Robert. 1995. "The Ten Commandments of OO Programming." comp.object, on Google Groups, March 16. Accessed 2019-05-23.
- Martin, Robert C. 2000. "Design Principles and Design Patterns." Accessed 2019-05-23.
- Martin, Robert. 2005. "The Principles of OOD." Accessed 2019-05-23.
- Martin, Robert. 2009. "Getting a SOLID start." Uncle Bob Consulting LLC., February 12. Accessed 2019-05-23.
- Merson, Paulo. 2020. "Principles for Microservice Design: Think IDEALS, Rather than SOLID." InfoQ, September 3. Accessed 2020-09-08.
- Millington, Sam. 2019. "A Solid Guide to SOLID Principles." Baeldung, February 10. Accessed 2019-05-23.
- MyPearsonStore. 2019. "Agile Software Development: Principles, Patterns, and Practices by Robert C. Martin." Pearson. Accessed 2019-05-23.
- Schults, Carlos. 2018. "In Defense of the SOLID Principles." Blog, NDepend, April 24. Accessed 2019-05-23.
- Tibi, Adam. 2013. "A Call To Drop “The Open Closed Principle” From The SOLID Design Principles." June 17. Accessed 2019-05-23.
- Wikipedia. 2019a. "Object-Oriented Software Construction." Wikipedia, February 03. Accessed 2019-05-23.
- Çapar, Ali Can. 2018. "SOLID -Single Responsibility Principle." December 19. Accessed 2019-05-23.
Further Reading
- Monterail. 2018. "SOLID Principles Cheatsheet." Accessed 2019-05-23.
- Martin, Robert C. 2000. "Design Principles and Design Patterns." Accessed 2019-05-23.
- Monus, Anna. 2019. "SOLID design principles: Building stable and flexible systems." Blog, Raygun, January 17. Accessed 2019-05-23.
- Kaminski, Ted. 2019. "Deconstructing SOLID design principles." April 02. Accessed 2019-05-23.
- Marston, Tony. 2011. "Not-so-SOLID OO Principles." June 08. Updated 2016-11-01. Accessed 2019-05-23.
- Liskov, Barbara H. and Wing, Jeannette M. 1994. "A Behavioral Notion of Subtyping." ACM Transactions m Programming Languages and Systems, vol. 16, no. 6, pp. 1811-1841, November 1994. Accessed 2019-05-23.
Article Stats
Cite As
See Also
- Object-Oriented Programming Concepts
- Security Principles
- Clean Code
- Design Patterns
- Design by Contract
- Clean Architecture