SOLID Programming Principles

SOLID programming is a collection of five programming patterns for object-oriented programming and design.  The definition of each of the principles is shown below.

S – Single Responsibility Principle (SRP) – A class should have one, and only one, reason to change.

O – Open/Closed Principle (OSP) – You should be able to extend an classes behavior without modifying it.

L – Liskov Substitution Principle (LSP) – Derived classes must be substitutable for their base classes.

I – Interface Segregation Principle (ISP) – Make fine grained interfaces that are client specific.

D – Dependency Inversion Principle (DIP) – Depend on abstractions, not on concretions.

Single Responsibility Principle (SRP)

This principle states that every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class.   All its services should be narrowly aligned with that responsibility.

As an example, consider a module that compiles and prints a report. Such a module can be changed for two reasons. First, the content of the report can change. Second, the format of the report can change. These two things change for very different causes; one substantive, and one cosmetic. The single responsibility principle says that these two aspects of the problem are really two separate responsibilities, and should therefore be in separate classes or modules. It would be a bad design to couple two things that change for different reasons at different times.

The reason it is important to keep a class focused on a single concern is that it makes the class more robust. Continuing with the foregoing example, if there is a change to the report compilation process, there is greater danger that the printing code will break if it is part of the same class.

Open/Closed Principle (OCP)

This principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.” When a single change to a program results in a cascade of changes to dependent modules, that program exhibits the undesirable attributes that we have come to associate with “bad” design.  The program becomes fragile, rigid, unpredictable and unreusable. The open/closed principle attacks this in a very straightforward way.  It says that you should design modules that never change.  When requirements change, you extend the behavior of such modules by adding new code, not by changing old code that already works.

The simplest way to apply OCP is to implement the new functionality on new derived (sub)classes that inherit the original class implementation. Another way is to mediate client access to the original class with an abstract interface, so new functionality can be implemented on new classes that are accessed through the same interface. Both ways create new classes and leave the original implementation untouched.

Liskov Substitution Principle (LSP)

It states that, in a computer program if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may be substitutes for objects of type T), without altering any of the desirable properties of that program (correctness, task performed, etc.)

Here’s an example of a violation of LSP

        void DrawShape(Shape s)
        {
              if (s.GetType() == typeof(Square))
                      DrawSquare((Square) s);
              else if (s.GetType() == typeof(Circle))
                      DrawCircle((Circle) s);
        }

        void DrawSquare(Square s)
        {

        }

        void DrawCircle(Circle s)
        {

        }

The problem with the DrawShape method is that it must know about every possible derivative of the Shape class, and it must be changed whenever new derivatives of Shape are created. Indeed, many view the structure of this function as anathema to Object Oriented Design.

Here’s a better way to implement the method so that it doesn’t have to know all the different types of shapes that need to be drawn.

    class Program
    {
        static void Main(string[] args)
        {
            DrawShape(new Square());
            DrawShape(new Circle());
        }

        static void DrawShape(IShape s)
        {
            s.Draw();
        }
    }

    public interface IShape
    {
        void Draw();
    }

    public class Square : IShape
    {
        public void Draw() { }
    }

    public class Circle : IShape
    {
        public void Draw() { }
    }

Interface Segregation Principle (ISP)

This principle says that once an interface has become too ‘fat’ it needs to be split into smaller and more specific interfaces so that any clients of the interface will only know about the methods that pertain to them. In a nutshell, no client should be forced to depend on methods it does not use.

Here’s an example of an interface used to describe an automobile.

    public interface IAutomobile
    {
        void StartEngine();

        void ApplyBrakes();

        void StopEngine();

        void DropTailGate();

        void EngageFourWheelDrive();
    }

The problem with this interface is that it has a couple of methods that wouldn’t apply to all automobiles. The DropTailGate and EngageFourWheelDrive methods wouldn’t apply to compact cars or minivans, so a new interface would need to be created for each of these with similar methods related to the engine and breaks.

A better design would have seperate interfaces for the specific features that exist for the different types of automobiles.

    public interface IAutomobile
    {
        void StartEngine();

        void ApplyBrakes();

        void StopEngine();
    }

    public interface ISupportsOffRoadDriving
    {
        void EngageFourWheelDrive();
    }

    public interface ITruck
    {
        void DropTailGate();
    }

The interfaces above prevent the duplication of the engine related methods to the other vehicle types and can be applied in addition to the IAutomobile interface.  For example, a class for Truck would implement the ITruck and IAutomobile interface.  If the truck has four wheel drive options, then it would also implement the ISupportsOffRoadDriving interace.

Dependency Inversion Principle (DIP)

The principle states:

A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.

The goal of the dependency inversion principle is to decouple high-level components from low-level components such that reuse with different low-level component implementations becomes possible. This is facilitated by the separation of high-level components and low-level components into separate packages/libraries, where interfaces defining the behavior/services required by the high-level component are owned by, and exist within the high-level component’s package. The implementation of the high-level component’s interface by the low level component requires that the low-level component package depend upon the high-level component for compilation, thus inverting the conventional dependency relationship

Here’s an example of a class that will create an automobile. The Automobile class has a dependency on the Engine class for the TurnIgnitionKey method. The code below is an example of a violation of the DIP principle.

    public class Application
    {
        public void CreateAutomobile()
        {
            var automobile = new Automobile();
        }
    }

    public class Automobile
    {
        Engine _engine;

        public Automobile()
        {
            _engine = new Engine();
        }

        public void TurnIgnitionKey()
        {
            _engine.StartEngine();
        }

    }

    public class Engine
    {
        public void StartEngine()
        {
            // ...
        }

        public void StopEngine()
        {
            // ...
        }
    }

Here’s a better implementation which conforms to the DIJ principle.  All the dependent classes are passed to the constructor of the Automobile class.

    public class Application
    {
        public void CreateAutomobile()
        {
            var automobile = new Automobile(new Engine());
        }
    }

    public class Automobile
    {
        Engine _engine;

        public Automobile(Engine engine)
        {
            _engine = engine;
        }

        public void TurnIgnitionKey()
        {
            _engine.StartEngine();
        }

    }

The example above now enables unit testing with mock objects.  This type of testing isolates testing to specific method behavior.  Any dependencies of the method are mocked which confirms that any test failures are isolated to the specific method being tested and none of it’s dependencies.

Useful links:

http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

http://en.wikipedia.org/wiki/Solid_%28object-oriented_design%29

http://blog.symprise.net/2009/06/open-closed-principle-concerns-about-change-in-software-design/

http://www.oodesign.com/dependency-inversion-principle.html

Advertisements

About Brian McKay
Software Architect

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: