SOLID Principles

SOLID are five class design principles espoused by Robert Martin (Uncle Bob Martin as he is also known), one of the progenitors of Object Oriented Design.   According to Uncle Bob one of the main purposes of these principles is to focus on the problem of dependency management.  That is to say, these principles assist in writing code that can be more easily extended, reused and tested.  Below I present a little synopsis of these principles.  I do this to allow myself to better understand them by explaining them on paper.  If someone else finds this useful, that’s great too.

I don’t see the below as gospel, but I do see these principles as immensely helpful as guidelines.  I try and put these to use in the systems I develop and at least have a good reason for violating them if I do.

SOLID stands for:

S:   Single Responsibility Principle (SRP)

This principle is mostly understood as a class should have one thing to do.  I’ve also heard this stated as a class should only have one reason to change.  If a class uses services in other objects the relationship should be decoupled enough that changes in those classes should not force changes in classes calling them.  A class itself, should only change because the behaviors it implements are changing.

The example given by Uncle Bob is the usage of rectangle class to handle both the geometric and UI display concerns of a rectangle.  The class does more than one thing; it handles the geometric calculations regarding a rectangle and the work to draw the rectangle to a UI.  Changes to either responsibility means the class will change.  This means that callers that are only using one part of the class may have to redeploy code, even though the change had no effect on the behavior they use.

O:   Open Closed Principle (OCP)

The Open Closed Principle suggests classes should be closed for modification, their source code should not be changed, but are open for extension.  The example of a class that breaks this principle is one that acts on an abstract type by casting it to a specific type in its implementation.  For instance if you had a ‘CarWash’ class with a method ‘Wash’ that took an array of Cars as an argument. If the method looked like the below it would violate OCP because you would have to change ‘CarWash’ every time you wanted to add a new type of car to wash.

carwash

To conform to OCP the above would better be implemented so that the ‘Car’ class has an abstract ‘Wash’ method that each separate Car type implements, then the ‘Wash’ method of ‘CarWash’ simply calls the ‘Wash’ method on each car.  In this manner ‘CarWash’ does not have to be changed to allow for new types of cars to be washed.

OCPCarWash

L:   Liskov Subsitution Principle (LSP)

The Liskov Substitution Principle states that methods that use variables of a base class type must be able to take any class derived from that base class without knowing it.  This seems pretty straight forward, but you must keep in mind that OO relationships are generally behavior based, so what might seem like an acceptable inheritance relationship may not be.

The example used by Uncle Bob is that of a square and a rectangle.  It seems reasonable to say that a square is a type of rectangle, so why not have a rectangle class and a square class that inherits from it?  The relationship is an ISA relationship, a square is a rectangle.

The problem is one of behavior.  A rectangle would most likely have the methods  ‘SetHeight’ and ‘SetWidth’.  What would these do in the ‘Square’ class?  A square doesn’t have a width and height all sides are equal.  Perhaps you could have both calls do the same thing in the square, so changing width and height are really the same call in the square.  What if you run into a caller who expects the following to be true:

Rectangle r = new Rectangle();
r.SetHeight(10);
r.SetWidth(5);
Assert.AreEqual(50, r.Area);

The problem you have here is that the above will fail for a square.  You cannot pass a square type in place of its parent in this case.  The situation occurs because geometrically speaking a square is a rectangle, but they do not behave the same. You know you’re violating LSP if your code looks like the below:

Rectangle r = new Rectangle();
r.SetHeight(10);
r.SetWidth(5);

if(r is Square)
Assert.AreEqual(25, r.Area);
else
Assert.AreEqual(50, r.Area);

I:   Interface Segregation Principle (ISP)

The Interface Segregation Principle is similar to the SRP but for exposed behavior.  This principle dictates that interfaces should expose highly cohesive behavior.  Callers should not have to acquire interfaces that have functionality they are not interested in mixed with the functionality they would like to use.  The logic here is similar to SRP.  A caller should not be forced to change their code because of a change in an interface for functionality that is not applicable to them.  The idea here is to minimize the impact of any change by only forcing those relying on behavior that is actually changing to have to accommodate the change.

D:   Dependency Inversion Principle (DIP)

This principle dictates that high level policy, or business rules, should be separated from the details of the low level implementation.  High level policy should depend on abstractions of low level implementations and low level details should depend on abstractions of high level policy.   The purpose of this is to allow the focus of higher level policy to be on itself and not the detail implementation, as well as allowing the high level policy to be reused over different lower level detailed implementations.

I’ve put this in practice by creating layers implemented as a set of interfaces.  The concrete implementations are returned by a layer provider.  The high level caller is only dependent on the interfaces.  The layer implements behavior based on the interfaces.  Both the caller and layer are unaware of each beyond the interface, the abstraction.

Leave a Reply

Your email address will not be published. Required fields are marked *