Saturday, April 19, 2008

Open Closed Principle

The principle states: software should be open for extension, but closed for modification.

  • They are "Open For Extension". This means that the behavior of the module can be extended. That we can make the module behave in new and different ways as the requirements of the application change, or to meet the needs of new applications.
  • They are "Closed for Modification". The source code of such a module is inviolate. No one is allowed to make source code changes to it.
Below is an example of some code breaking this principle:
Class: WeatherDisplay
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace OCP.a.problem
{
    public class WeatherDisplay
    {
        private readonly DisplayTemperature _DisplayTemperature;
        private readonly DisplayWindSpeed _DisplayWindSpeed;

        public WeatherDisplay(int temperature, int windSpeed)
        {
            _DisplayTemperature = new DisplayTemperature(temperature);
            _DisplayWindSpeed = new DisplayWindSpeed(windSpeed);
        }

        public void DisplayWeather()
        {
            Console.WriteLine(_DisplayTemperature.Temperature());
            Console.WriteLine(_DisplayWindSpeed.WindSpeed());
        }
    }
}
Class: DisplayTemperature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace OCP.a.problem
{
    public class DisplayTemperature
    {
        private readonly int _Temperature;

        public DisplayTemperature(int temperature)
        {
            _Temperature = temperature;
        }

        public string Temperature()
        {
            return("The current temperature is: " + _Temperature + " degrees celcius.");
        }    
    }
}
Class: DisplayWindSpeed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace OCP.a.problem
{
    public class DisplayWindSpeed
    {
        private readonly int _WindSpeed;

        public DisplayWindSpeed(int windSpeed)
        {
            _WindSpeed = windSpeed;
        }

        public string WindSpeed()
        {
            return("The current wind speed is: " + _WindSpeed + " meters per second.");
        }    
    }
}
In this example you can see that if the implementation of ProvideTemperature or ProvideWindSpeed needed to be extended to also work on different scales; than you would have to change the class WeatherDisplay to use the new classes. What if even more changes or extensions are needed in the future; than you would constantly be altering the class WeatherDisplay and at every other place these classes are being used, that is the violation of the Open Closed Principle. The class WeatherDisplay is not open for extensions and closed for modifications.

Below you can see a solution that would obey the Open Closed Principle using the Strategy Pattern:
Interface: IDisplayTemperature
1
2
3
4
5
6
7
namespace OCP.b.solution
{
    public interface IDisplayTemperature
    {
        string Temperature();
    }
}
Interface: IDisplayWindSpeed
1
2
3
4
5
6
7
namespace OCP.b.solution
{
    public interface IDisplayWindSpeed
    {
        string WindSpeed();
    }
}
Class: WeatherDisplay
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace OCP.b.solution
{
    public class WeatherDisplay
    {
        private readonly IDisplayTemperature _DisplayTemperature;
        private readonly IDisplayWindSpeed _DisplayWindSpeed;

        public WeatherDisplay(int temperature, int windSpeed)
        {
            _DisplayTemperature = new DisplayTemperature(temperature);
            _DisplayWindSpeed = new DisplayWindSpeed(windSpeed);
        }

        public void DisplayWeather()
        {
            Console.WriteLine(_DisplayTemperature.Temperature());
            Console.WriteLine(_DisplayWindSpeed.WindSpeed());
        }
    }
}
Class: DisplayTemperature : IDisplayTemperature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace OCP.b.solution
{
    public class DisplayTemperature : IDisplayTemperature
    {
        private readonly int _Temperature;

        public DisplayTemperature(int temperature)
        {
            _Temperature = temperature;
        }

        public string Temperature()
        {
            return("The current temperature is: " + _Temperature + " degrees celcius.");
        }    
    }
}
Class: DisplayWindSpeed : IDisplayWindSpeed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace OCP.b.solution
{
    public class DisplayWindSpeed : IDisplayWindSpeed
    {
        private readonly int _WindSpeed;

        public DisplayWindSpeed(int windSpeed)
        {
            _WindSpeed = windSpeed;
        }

        public string WindSpeed()
        {
            return("The current wind speed is: " + _WindSpeed + " meters per second.");
        }    
    }
}
When we implement the WeatherDisplay class in this way than it is open for extensions but closed for modifications. We could provide any implementation of the ITemperatureProvider and IWindSpeedProvider without the need to make any modifications to the class. For example:
Class: DisplayTemperatureInCelcius : IDisplayTemperature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace OCP.b.solution
{
    public class DisplayTemperatureInCelcius : IDisplayTemperature
    {
        private readonly int _Temperature;

        public DisplayTemperatureInCelcius(int temperature)
        {
            _Temperature = temperature;
        }

        public string Temperature()
        {
            return("The current temperature is: " + _Temperature + " degrees celcius.");
        }
    }
}
Class: DisplayTemperatureInFahrenheit : IDisplayTemperature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace OCP.b.solution
{
    public class DisplayTemperatureInFahrenheit : IDisplayTemperature
    {
        private readonly int _Temperature;

        public DisplayTemperatureInFahrenheit(int temperature)
        {
            _Temperature = temperature;
        }

        public string Temperature()
        {
            double fahrenheit = (212 - 32) / 100 * _Temperature + 32;
            return ("The current temperature is: " + fahrenheit + " degrees fahrenheit.");
        }
    }
}
But now the WeatherDisplay class is not completely open for extensions what happens when you want to also display humidity information, than you would have to open up the WeatherDisplay class and make the needed adjustments. The below example demonstrates how to solve this problem:
Interface: ITextDisplay
1
2
3
4
5
6
7
namespace OCP.c.final.solution
{
    public interface ITextDisplay
    {
        void Show(TextWriter writer);
    }
}
Class: WeatherDisplay
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace OCP.c.final.solution
{
    public class WeatherDisplay
    {
        private readonly IEnumerable<ITextDisplay> _Displays;

        public WeatherDisplay(IEnumerable<ITextDisplay> displays)
        {
            _Displays = displays;
        }

        public void DisplayWeather()
        {
            foreach (ITextDisplay display in _Displays)
            {
                display.Show(Console.Out);
            }
        }
    }
}
Class: DisplayTemperatureInCelcius : ITextDisplay
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace OCP.c.final.solution
{
    public class DisplayTemperatureInCelcius : ITextDisplay
    {
        private readonly int _Temperature;

        public DisplayTemperatureInCelcius(int temperature)
        {
            _Temperature = temperature;
        }

        public void Show(TextWriter writer)
        {
            writer.WriteLine("The current temperature is: " + _Temperature + " degrees celcius.");
        }
    }
}
Class: DisplayHumidity : ITextDisplay
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace OCP.c.final.solution
{
    public class DisplayHumidity : ITextDisplay
    {
        private readonly int _Humidity;

        public DisplayHumidity(int humidity)
        {
            _Humidity = humidity;
        }

        public void Show(TextWriter writer)
        {
            writer.WriteLine("The current humidity is: " + _Humidity + " percent.");
        }
    }
}
As you can see in the previous example you can now provide multiple ITextDisplay implementations, now the WeatherDisplay class is completely closed for modifications and open for extensions.

I hope you found these examples helpful. Also take a look at the following links:

http://www.objectmentor.com/resources/articles/ocp.pdf
http://www.lostechies.com/blogs/joe_ocampo/archive/2008/03/21/ptom-the-open-closed-principle.aspx
http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
http://en.wikipedia.org/wiki/Strategy_pattern

Code download: http://downloads.fohjin.com/OCP.zip

No comments: