Friday, April 18, 2008

Single Responsibility Principle

The principle states: There should never be more than one reason for a class to change. 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
21
22
23
24
25
26
27
28
29
namespace SRP.a.problem
{
    public class WeatherDisplay
    {
        private readonly int _Temperature;
        private readonly int _WindSpeed;

        public WeatherDisplay(int temperature, int windSpeed)
        {
            _Temperature = temperature;
            _WindSpeed = windSpeed;
        }

        public void DisplayWeather()
        {
            DisplayTemperature();
            DisplayWindSpeed();
        }

        private void DisplayTemperature()
        {
            Console.WriteLine("The current temperature is: " + _Temperature + " degrees celcius.");
        }
        private void DisplayWindSpeed()
        {
            Console.WriteLine("The current wind speed is: " + _WindSpeed + " meters per second.");
        }
    }
}
As you can see there are 2 distinct responsibilities for the class WeatherDisplay (you could even say there are 3 responsibilities). You could say one is to display the temperature and the other is to display the wind speed. Now because we are talking about 2 responsibilities we can also say that there are at least 2 reasons why we would / could change this class in the future, the temperature should be displayed in Fahrenheit and the wind speed should be displayed in knots.

Below we see how we could solve this and be coding according to the Single Responsibility Principle.
Class: WeatherDisplay
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace SRP.b.solution
{
    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()
        {
            _DisplayTemperature.Display();
            _DisplayWindSpeed.Display();
        }
    }
}
Class: DisplayTemperature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace SRP.b.solution
{
    public class DisplayTemperature
    {
        private readonly int _Temperature;

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

        public void Display()
        {
            Console.WriteLine("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 SRP.b.solution
{
    public class DisplayWindSpeed
    {
        private readonly int _WindSpeed;

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

        public void Display()
        {
            Console.WriteLine("The current wind speed is: " + _WindSpeed + " meters per second.");
        }
    }
}
In this second example you can see that I now re-factored the class WeatherDisplay into three classes. Weather Display still exists but now it is using the class TemperatureDisplay and WindSpeedDisplay to actually display the data.

As I said before you could even say that there were 3 responsibilities for the first example and they would be displaying the information, providing temperature text markup and providing wind speed text markup. So in that case we would probably re-factor our example in the following way:
Interface: ITextDisplay
1
2
3
4
5
6
7
namespace SRP.c.final.solution
{
    public interface ITextDisplay
    {
        void Display(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 SRP.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.Display(Console.Out);
            }
        }
    }
}
Class: DisplayTemperature : ITextDisplay
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace SRP.c.final.solution
{
    public class TemperatureDisplay : ITextDisplay
    {
        private readonly int _Temperature;

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

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

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

        public void Display(TextWriter writer)
        {
            writer.WriteLine("The current wind speed is: " + _WindSpeed + " meters per second.");
        }
    }
}
As you can see now instead of providing the class WeatherDisplay with a fixed number of parameters we can now provide it with an unlimited amount of ITextDisplay implementations. The class WeatherDisplay is now responsible for providing a proper TextWriter and telling each ITextDisplay implementation to display its information. The ITextDisplay implementations now only have to write its information to the provided writer, and thus also implementing the Liskov Substitution Principle.

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

http://www.objectmentor.com/resources/articles/srp.pdf
http://www.lostechies.com/blogs/sean_chambers/archive/2008/03/15/ptom-single-responsibility-principle.aspx
http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

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

No comments: