SOLID is an acronym for several specific traits that are consistently found in well written, reusable, and extendable software. Specifically, there are five principals to follow when constructing object orientated systems: SRP, OCP, LSP, ISP, and DIP.
SRP– Single Responsibility Principle
SRP relates to class cohesion. In essence a class should have no more than one reason to change. A class violating this principle is likely doing too much and thus lacking cohesion. Classes that lack cohesion are brittle and difficult to maintain.
For example, here is a class does more than one thing or in other words has more than one purpose or responsibility:
In this case we have a class that mixes both database activities (responsibility #1) and the abstraction of the real world entity that the class represents (responsibility #2). Classes like this can be refactored into more cohesive pieces by using a well defined pattern such as Data Transfer Objects and Data Access Objects (DTO/DAO):
By separating the purpose into separate classes, it becomes clearer what the intended purpose of each class is, increases maintainability and promotes code reuse.
OCP – Open/Closed Principle
The OCP principle states that objects (and other software artifacts) should be open for extension, but closed for modification. What this means is that you should be able to extend a classes behavior without modifying it internally and potentially breaking any other usages or users of the class or artifact.
The OCP principle is typically enforced via inheritance and abstract or virtual classes and methods. Re-using our example we introduced in SRP let’s make up the following scenario: assume that we not only want to persist our objects to the database, but we also want to now persist the XML representation of the file to disk. We could chose to modify the existing PersonDAO class by adding a new methods for writing the xml to disk, reading the xml off disk and deleting the file from disk :
This of course would/could potentially break any clients using the DAO. A better way to accomplish this is to create a base class or interface that provides the signatures of the methods and/or a base implementation and then have specific derived classes provide specific implementations:
In nutshell, OCP deals with increasing the maintainability and reusability of code, which is achieved by extending existing code by introducing new subtypes as opposed to modifying older already working code when new behavior or features are required.
LSP – Liskov Substitution Principle
LSP is another principle related to class structure and states that any derived classes must be substitutable for their base classes. What this means is that the base class must be able to use references of derived classes, without knowing that it is. Like OCP is closely related to inheritance and polymorphism and mutability of the objects state leading to a violation of one the classes invariants.
The classic example is as follows: a square that derives from a rectangle with getter and setter methods or properties for height and width. Because the height and width can be changed independently, it’s possible to violate the fact that a square has sides of equal length.
Can violate the LSP easily:
However, by refactoring the class to ensure that each property is not modified independently, we can preserve the class invariant (a square is a rectangle with equal sides):
LSP defines a several behavioral conditions that subtypes must adhere to, namely:
- Invariants of the base types must be preserved in subtypes
- Post conditions cannot be weakened in a subtype
- Pre conditions cannot be strengthened by a subtype
ISP – Interface Segregation Principle
ISP largely relates to class cohesiveness. Classes with high cohesion tend to do less, but do it much better and easier to reuse and maintain. Classes with low cohesiveness will tend to lots of things resulting in unwarranted dependencies, which reduce maintainability, reliability, testability, and understandability.
Historically ISP has dealt with the of “fat interfaces” leading to “fat classes” that are not very cohesive. “Fat interfaces” is a term for interfaces with many methods that can and should be broken into smaller, more fine grained interfaces. In situations where “fat interfaces” are required, abstract base classes that implement the more cohesive fines grained interfaces should be used. The major theme here is that clients should not be required to depend upon interfaces that they do not need [Uncle Bob].
For an example of an ISP violation, we do not have to look far in the .NET world (note that this is my opinion and my opinion only) to find one: System.Xml.Serialization.IXmlSerializable. For those not familiar with this interface it provides a means of implementing custom XML serialization. It’s a very simple interface in that it only provides three method signatures to implement: ReadXml, WriteXml, and GetSchema.
It’s also very useful, in that the .NET framework provides a corresponding class for serializing and de-serializing objects marked with the IXmlSerializable interface: XmlSerializer. However, what happens if you only need serialization or de-serialization and not both? What if schema validation is an overkill, and is in fact a reserved method that should not be used? You end up with GetSchema and either ReadXml or WriteXml throwing a NotImplementedException!
The ISP violation in the Person class can be refactored by providing an abstract class that implements IXmlSerializable and then choosing the methods you wish and then overriding the methods you wish to implement.
Now, you are no longer forcing clients to implement parts of the interface that are not of concern to them.
DIP – Dependency Inversion Principle
DIP is concerned with the structural relationship between classes and the effect that dependencies have on the quality and maintainability of software. Formally, it is the concept that you should depend upon abstractions and not upon concretions [wiki]. DIP is associated with Dependency Injection (DI) and the Invocation of Control (IoC) containers that are commonly used as means of abstracting the construction of objects from their use. It’s sounds confusing, but it’s really not.
The two major tenets of DIP are as follows:
- High level models should not depend upon low level modules; both should depend upon abstractions
- Abstractions should not depend upon details; details should depend upon abstractions
What this means is that systems should be comprised of a series of layers, from the abstract and generic to the concrete and specific. Changes in lower level modules should not effect or cause higher level modules to change; the opposite should be true. The references that are used should be made by using abstract class or interfaces and not concrete representations.
Here is an example of a DIP violation:
Note that the PersonDAO class depends upon the concrete SqlConnection implementation. Now this is fine and dandy if you are working in environment where you are always using a SQL Server specific provider, however what happens if you want reuse this code in an environment that is using a MySql provider? In that case, you really can’t without adding some horrible dependencies.
Here is a better example of the same code refactored so that it uses the abstraction (DbConnection) instead of a concrete representation (SqlConnection):
Notice that the refactored object model is using two forms of Dependency Injection: constructor injection and method injection.
Dependency Injection provides a means of injecting dependencies into an object as opposed to hard coding the dependencies within the object itself. Classes designed in this matter lend themselves to both unit testing via mocking or stubbing and construction by way of IoC containers.