📜 ⬆️ ⬇️

SOLID: the principle of sole responsibility

In this article we will try to describe one of the well-known principles of object-oriented programming, which is an abbreviation of the no less well-known concept of SOLID. In English, it is called Single Reponsibility, which in Russian means Uniqueness of Responsibility.

In the original definition, this principle states:

The class should have only one reason to change.
')
To begin with, let's try to define the concept of Responsibility and try to connect this concept in the above wording. Any software component has some reasons why it was written. They can be called requirements. Ensuring that the implemented logic follows the requirements imposed on the component is called the responsibility of the component. If requirements change, the component logic, and therefore its responsibility, also changes. Thus, the original formulation of the principle is equivalent to the fact that a class should have only one responsibility, one appointment. Then the reason for his change will be one.
To begin with, we will give an example of violation of the principle and see what consequences this may have. Consider a class that can calculate the area of ​​a rectangle, as well as display it on a graphical interface. Thus, a class combines two responsibilities (and therefore two global reasons for change), which can be defined as follows:

  1. The class must be able to calculate the area of ​​the rectangle on its two sides;
  2. The class must be able to draw a rectangle.

Below is a sample code:
#using UI; class RectangleManager { public double W {get; private set;} public double H {get; private set;} public RectangleManager(double w, double h) { W = w; H = h; // Initialize UI } public double Area() {    return W*H; } public void Draw() {    // Draw the figure on UI } } 

Note that the above code uses third-party graphic components for drawing, implemented in the UI namespace.

Suppose there are two client programs that use this class. One of them just performs some calculations, and the second implements the user interface.

Program 1:

 #using UI; void Main() { var rectangle= new RectangleManager(w, h);  double area = rectangle.Area();  if (area < 20) { // Do something; } } 

Program 2:

 #using UI; void Main() {   var rectangle= new RectangleManager(w, h);  rectangle.Draw(); } 

This design has the following disadvantages:


In this case, there are signs of poor design, in particular Fragility (it is easy to break when making changes due to high connectivity), as well as relative Immobility (possible difficulties using the class in Program 1 due to unnecessary UI dependency).

The problem can be solved by dividing the original RectangleManager component into the following parts:

  1. The Rectangle class, responsible for calculating the area and providing the lengths of the sides of the rectangle;
  2. A RectanglePresenter class that implements a rectangle drawing.

Please note that the responsibility of the Rectangle class is complex, that is, it contains both the requirements for providing side lengths and area calculation. Thus, it can be said that responsibility reflects a component contract, that is, a set of its operations (methods). This contract itself is determined by the potential needs of customers. In our case, this is a representation of the geometric parameters of the rectangle. In code, it looks like this:

 public class Rectangle { public double W {get; private set;} public double H {get; private set;} public Rectangle(double w, double h) { W = w; H = h; } public double Area() {   return W*H; } } public class RectanglePresenter() { public RectanglePresenter() { // Initialize UI } public void Draw(Rectangle rectangle) {  // Draw the figure on UI } } 

Taking into account the changes made, the client program code will look as follows:

Program 1:

 void Main() { var rectangle= new Rectangle(w, h);  double area = rectangle.Area();  if (area < 20) { // Do something } } 

Program 2:

 #using UI; void Main() { var rectangle = new Rectangle(w, h); var rectPresenter = new RectanglePresenter();  rectPresenter.Draw(rectangle); } 

This shows that Program 1 is no longer dependent on graphic components. In addition, as a result of following the principle, unnecessary dependencies disappeared, the code became more structured and reliable.

In most cases, the principle of Uniqueness of Responsibility helps to reduce the connectivity of components, makes the code more readable, simplifies the writing of unit tests. But you should always remember that this is just a general recommendation, and the decision on its use should be made on the basis of a specific situation. The division of responsibility must be conscious. Here are some examples of when this is not worth doing:
  1. Breaking up an existing class may cause the client code to fail in a banal way. It is difficult to notice this at the design and testing stage if the logic is not sufficiently covered by high-quality unit tests and / or due to poor manual / auto testing. Sometimes such a breakdown can cost companies money, reputation, etc .;
  2. Separation of responsibilities is simply not necessary, as the client code and component developers are satisfied with everything (they know about the existence of the principle). Requirements practically do not change. And this applies to both existing classes and those that have not yet been created, but are at the design stage;
  3. In other cases, when the benefits of separation are less than harm from it.

However, the knowledge and understanding of the principle should improve the developer’s outlook, which will allow him to more effectively design and maintain the solutions being created.

Source: https://habr.com/ru/post/328584/


All Articles