Tuesday, 5 September 2017

Single Responsibility Principle

Introduction 

This article will give an explanation of the Single Responsibility Principle (SRP) and will show a simple example in C#.

Background

What

There should never be more than one reason for a class to change. The SRP is one of the simplest of the SOLID principles but also one of most difficult to get right. Collecting and gathering responsibilities at one place is a common thing to do and happens in a natural way. Finding and separating those responsibilities from one another is much of what software design is really about.

Why

When a class has more than one responsibility, there are also more triggers and reasons to change that class. A responsibility is the same as “a reason for change” in this context. Changes to one responsibility may impair or inhibit the class’ ability to meet the others. This kind of coupling leads to fragile designs that break in unexpected ways when changed. If a class has more than one responsibility, then the responsibilities become coupled. Also when a (functional) responsibility is divided over more than one class, all classes part of that responsibility have to changed. They are caught in the chain of change. Always try to give every class its own and unique responsibility.

How

Separating responsibility can be done by defining for every responsibility a class or an interface. It makes no difference, a class can be seen as nothing more than a container of code, like a file or a library. What's important is that responsibilities are separated by abstraction and therefore can be implemented by different consumers of that interface.

Using the Code

The following C# example shows a class named "RectangleShape" that implements two methods, one that calculates its rectangle area and one that draws the rectangle. When the area calculation changes for some reason or the drawing method has to change, for example, another fill color is used, then the whole class is under change. Also if the properties are altered, it influences both methods. After a code change, the class must be tested as a whole again. There is clearly more than one reason to change this class.

Problem

/// <summary>
/// Class calculates the area and can also draw it on a windows form object.
/// </summary>

    public class RectangleShape
    {
        public int Height{ get; set; }
        public int Width { get; set; }
 
        public int Area()
        {
            return Width * Height;
        }
 
        public void Draw(Form form)
        {
            SolidBrush myBrush = new SolidBrush(System.Drawing.Color.Red);
            Graphics formGraphics = form.CreateGraphics();
            formGraphics.FillRectangle(myBrush, new Rectangle(0, 0, Width, Height);
        }
    }   
Typically, the above class is used by consuming client classes like these:
/// <summary>
/// Consumes the RectangleShape */
/// </summary>
    public class GeometricsCalculator
    {
        public void CalculateArea(RectangleShape rectangleShape)
        {
            int area = rectangleShape.Area();
        }
    }   


/// <summary>
//// Consumes the RectangleShape */
/// </summary>
    public class GraphicsManager
    {
        public Form form {get;set;}

        public void DrawOnScreen(RectangleShape rectangleShape)
        {
            rectangleShape.Draw(form);
        }
    }

Solution

The next classes show how to separate the different responsibilities. Basic coding is used not taking other SOLID principles into account. It only just shows how to deal with the SRP principle. The RectangleDraw class consumes now a RectangleShape instance and a Form object.
/// <summary>
/// Class calculates the rectangle's area.
/// </summary>
    public class RectangleShape
    {
        public int Height { get; set; }
        public int Width { get; set; }

        public int Area()
        {
            return Width * Height;
        }
    }

/// <summary>
/// Class draws a rectangle on a windows form object.
/// </summary>
    public class RectangleDraw
    {
        public void Draw(Form form, RectangleShape rectangleShape)
        {
            SolidBrush myBrush = new SolidBrush(System.Drawing.Color.Red);
            Graphics formGraphics = form.CreateGraphics();
            formGraphics.FillRectangle(myBrush, 
            new Rectangle(0, 0, rectangleShape.Width,rectangleShape.Height));
        }
    }
The following code shows how to consume both classes:
/// <summary>
/// Consumes the RectangleShape */
/// </summary>
    public class GeometricsCalculator
    {
        public void CalculateArea(RectangleShape rectangleShape)
        {
            int area = rectangleShape.Area();
        }
    }

/// <summary>
/// Consumes the RectangleDraw and RectangleShape */
/// </summary>
    public class GraphicsManager
    {
        public Form form { get; set; }
        
        public void DrawOnScreen(RectangleDraw rectangleDraw, RectangleShape rectangleShape)
        {
            rectangleDraw.Draw(form, rectangleShape);
        }
    }      

No comments:

Post a Comment