S.O.L.I.D. is a collection of best-practice object-oriented design principles that has been observed in industry, and first documented in the mid 1990’s by Robert Martin. These practices can be applied to most designs to accomplish a number of desirable goals including loose-coupling, higher maintainability, re-use, testability, etc.
The SOLID acronym is (ironically enough) for a further set of acronyms, these stand for:
SRP: Single responsibility
OCP: Open/Closed principle
LSP: Liskov’s principle
ISP: Interface segregation principle
DIP: Dependency injection principle
SRP: Single Responsibility Principle / Separation Of Concerns
This is probably the largest of the principles and probably most difficult to understand and get right. This principle can be summarised as “A class should only have one reason to change”.
A vast number of design patterns can be defined under this principle. Take a look at presentation patterns alone: Supervising controller, Passive view, Front Controller, Page controller, etc. all of which originate in one form or the other from one of the two main patterns MVP and MVC (MVC originating from the Smalltalk era).
Another example that I like to mention is systems that use an ORM (nHibernate, ADO.Net for Entities, Linq to SQL, etc) of some sort. Believe it or not the key driver for using an ORM is to separate the persistence logic from you business logic, i.e. separating the concerns.
So why bother with something like SRP. The big one: maintenance, also re-use and testability. By only having one concern you tend to have more granular system that has a greater scope for testability as well as not having to build the entire integrated system to test a small part of it.
OCP: Open/Closed Principle
“Entities or classes must be open for extension but closed for modification.”
I would categorise this as being aimed mainly at systems that have been deployed into production, so rephrased, you may not alter a class or entity once in production. The only way of changing or adding behaviour is through extension. This would cause production deployed code to suffer fewer changes leading to a smaller surface area for bugs when changes are deployed. Practically I believe this principle will have a very close relationship with the dependency injection principle. As one of the other principles stated in the PrinciplesOfOod article by Robert Martin: “Abstractness increases with stability”. So even though (as I mentioned previously) I believe this to apply to systems that are production deployed, this does incur a design overhead. The system must cater for a decent level of abstractness. So why would we incur this overhead to development: greater maintainability and greater re-use within a system.
LSP: Liskov Substitution Principle
“Functions that use references to base classes must be able to use objects of derived classes without knowing it.”
This principle was first published by Barbara Liskov and is closely related to design by contract. When this basic principle is adhered to applications intrinsically become more maintainable, reusable and robust. And looking at OCP these two can be used together to achieve extensibility very effectively.
When a method requires a parameter, try and specify the most abstract version of what is required. For example: in the .Net world, using the IList<T> interface instead of a concrete List<T> when requiring a collection, assuming that the functionality required is exposed through the interface. This obviously achieves greater flexibility for the consumer in deciding what implementation of IList<T> he/she will call the method with. And as long as the implementation adheres to the IList<T> interface and behaves as expected, the code in the method would work as usual, not caring about the details of the implementation
ISP: Interface Segregation Principle
“Make fine grained interfaces that are client specific.”
Practically if there are interfaces or abstract classes the consumer of these classes should not be forced to implement sections that they don’t care about or won’t use, rather break them up into finer grained interfaces. This would also cause developers of these interfaces or abstractions to yield to the single responsibility principle, as these interfaces/ abstractions become more focussed on the intent of the implementation. A very good example that is generally referred to is the .Net “MembershipProvider” base class. When a custom implementation is implemented, there is more often than not a large number of functionality that has no implementation, but still needs to be defined.
DIP: Dependency Inversion Principle
“Depend on abstractions and not implementations or concretions.”
One of my favourite principles, I believe that this is a basic foundation of good development practice. Practically this principle means that lower level objects are injected into higher level objects. This results in components being loosely coupled with a higher degree of separation of concerns. Due to the higher-level components not being coupled with low level functionality, this makes it complimentary to an effective unit testing strategy. So taking these principles into consideration a good class design would only have dependencies on contracts (interfaces/abstractions) outside of itself. These dependencies are injected by the calling code by applying a technique called Dependency Injection. There is a large number of these containers that can be used, each with its own strengths and weaknesses. To mention a few: Microsoft’s Unity, StructureMap, Castle’s Windsor, Ninject, and the list goes on.
Conclusion
In my opinion, the essence of SOLID can be summarised as a set of logical practices that was observed in industry, and formalised for the benefit of everyone to enhance the overall quality and lifespan of any development effort. I do believe there are numerous variations and interpretations of the principles, but it is nonetheless a topic for serious consideration when designing or developing any system.
References:
Robert (Uncle Bob) Martin, The Principles of OOD
Chad Myers: SOLID principles