The Open Closed Principle: A Simplified Example

You’ve no doubt heard of the application architecture acronym, “SOLID”. Bob Martin (Uncle Bob) came up with this as a guideline for application architecture. The acronym stands for:

  1. Single Responsibility Principle (a class should have one and only one reason to change)
  2. Open Closed Principle (a class should be open for extension but closed for change)
  3. Liskov’s Substitution Principle (subclasses implementations should do what you expect them to do)
  4. Interface Segregation Principle (classes shouldn’t have to implement interfaces they don’t need)
  5. Dependency Inversion Principle (don’t tie implementation to concrete dependencies, but instead code to abstractions (i.e.: interfaces))

When I talk to various folks, I find that a lot of people don’t really get the Open / Closed Principle. To that end, I decided I’d do a quick write up which probably doesn’t do what Uncle Bob was expressing much justice at all, but is hopefully very simple to understand.

In a nutshell, the behavior of a piece of code should be able to be extended without changing it. That sounds very academic to me, doesn’t really make a lot of sense, and doesn’t seem to have much real world value. After all, a solution should only be as complicated as the problem you are trying to solve. Well, another way of looking at this is to say that code which has been tested and put in production shouldn’t be changed. This reduces the chance of introducing new bugs in existing code and thus keeps total cost of ownership down (less bugs = less $$). Ok, that makes a little more sense, but how do you do it?

It’s easier to see this principle in action. Thus, have a look at the code below.

This first code block violates the Open Closed Principle. A dead give away (or commonly code a “code smell”) is the switch case statement. Be on the lookout for those in your code; they are often good indicators of a worthwhile refactoring opportunities.

VIOLATION:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;


namespace ConsoleApplication1
{
    /*
    * This example is a violation the Open Closed Principle (the O in SOLID), 
    * which means that code is "open for extension, but closed for change".
    * 
    * Consider that the FeedTheAnimals method is far more 
    * complicated and has been tested
    * and moved to production. 
    * 
    * If you need to add new functionality
    *  for a new animal, such as a cow, you have to
    * change code which has already been tested. 
    * 
    *  This isn't truly extensible.
    */

    public enum AnimalType
    {
        Dog,
        Cat,
        Bird
    }


    public class OpenCloseViolation
    {
        public void FeedTheAnimals(AnimalType animalType)
        {
            switch (animalType)
            {
                case AnimalType.Dog:
                    {
                        Trace.Write("eat meat");
                        break;
                    }
                case AnimalType.Cat:
                    {
                        Trace.Write("eat fish");
                        break;
                    }
                case AnimalType.Bird:
                    {
                        Trace.Write("eat worms");
                        break;
                    }
                default:
                    {
                        Trace.Write("eat grass");
                        break;
                    }
            }
        }

        public void DoWork()
        {
            this.FeedTheAnimals(AnimalType.Dog);
            this.FeedTheAnimals(AnimalType.Cat);
            this.FeedTheAnimals(AnimalType.Bird);
        }
    }
}

By contrast, the following code conforms to the Open Closed Principle:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication1
{
    /*
     * This example conforms to the Open Closed Principle
     * (the O in SOLID),
     * which means that code is
     * "open for extension, but closed for change".
     *
     * In this example, the implementation is left
     * to the subclasses and
     * FeedTheAnimals never has
     * to change to add new functionality.
     *
     * Thus, this is truly extensible.
     */

    public class Animal
    {
        public virtual void Eat()
        {
            Trace.Write("eat grass");
        }
    }

    public class Dog : Animal
    {
        public override void Eat()
        {
            Trace.Write("eat meat");
        }
    }

    public class Cat : Animal
    {
        public override void Eat()
        {
            Trace.Write("eat fish");
        }
    }
    public class Bird : Animal
    {
        public override void Eat()
        {
            Trace.Write("eat worms");
        }
    }


    public class OpenCloseConformance
    {
        public void FeedTheAnimals(Animal animal)
        {
            animal.Eat();
        }

        public void DoWork()
        {
            Animal dog = new Dog();
            this.FeedTheAnimals(dog);

            Animal cat = new Cat();
            this.FeedTheAnimals(cat);

            Animal bird = new Bird();
            this.FeedTheAnimals(bird);
        }
    }
}

That’s it. It’s a lot easier than it sounds. I hope this helps someone.

Happy coding,

Tom Hundley
Elegant Software Solutions

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