How to not understand the principles of development of SOLID architecture

There is a problem with the description and interpretation of the principles of development of the SOLID architecture (authorship of Robert Martin). Many sources give their definition and even examples of their use. Studying them and trying to try on using myself, I constantly caught myself thinking that there was not enough explanation of the magic of their application. And trying to see the internal gears, to understand - and for me it means to remember - laid them out in their "terms-shelves." Well, if it will be useful to someone else.


We proceed to "juggle the shelves" of the above design approach.

Single Responsibility Principle (SRP) sole responsibility principle

One piece of code should change only during the implementation of one goal. If a section of code implements two tasks and changes for different uses, then you should duplicate this section in an instance for each purpose. This is very important because it requires a departure from the generally accepted principle of eliminating duplication.

The purpose of this principle is to eliminate implicit errors introduced due to the fact that the following invariants exist for the development of a code section, procedure, class, component (hereinafter, the term [component] is used to combine these concepts):

  • [1] correctly written [component] is necessarily used and more often several times,
  • [2] at each place of use, [component] is expected to maintain a consistent behavior leading to a repeatable result,
  • [3] when using the [component] in several places, the result should satisfy each place of use,
  • if a change in [component] is required for one of the places of use, and the previous behavior of [component] is required for another place of use, it is necessary to create a copy of [component] and then modify it (or generalize [component] with additional parameters that provide different behavior),
  • if there are places of use of the [component] that are not important for the current task solved by the programmer, then it is very easy for him to forget about checking compatibility with these places of use of the changes made to this [component].

    Therefore, all places of use should be located in the [Single Responsibility] zone of a single responsibility, that is, be changed and taken into account at once for any problem solved by the programmer).

    The principle applies both to a section of code and to a component, library, program, set of programs used in several places.

    Many sources give an example of a class with only one “function” as the ideal of SRP and the class of “divine object”, combining all the functions of the application, as an antipattern. An IMHO class with only one “function” is a requirement for premature optimization of the code architecture, prompting to write many classes (code entities) from scratch, while forgetting that the absence of more than one place of use allows the programmer to quickly evaluate a small amount located locally (in one class) interacting code than to analyze the external relations of disparate code entities responsible for their "function". A “divine object” for a tiny application is also not a strong crime - it allows you to start development: select all the necessary entities and, writing them side by side, separate from the external objects of the standard library and external modules (create a living cell and separate it with a membrane). In the process of growth and development of the project, there are many methods that help to follow the SRP, one of them is the division into classes and minimizing the number of "functions" for which each class is responsible (cell division and their specialization in the body).

    Here I would like to write out a set of techniques for maintaining SRP, but this work has not yet been completed (I hope "hands reach"). From the obvious areas where you can look for these tricks:

  • design patterns;
  • using different specialized component branches, as opposed to creating a component that satisfies all application methods (fork on GitHub).

Open-Closed Principle (OCP) Open / Closed Principle

It is optimal to plan the development of the code so that for the programmer to implement new tasks it is necessary to add new code, while the old code does not need changes. The code must be open (Open) to add and closed (Closed) to change.

The purpose for this principle is to minimize labor costs and eliminate implicit errors introduced due to the following invariants in development:

  • [1], [2], [3] described previously,
  • to implement a new task, the programmer can add new [components] or change the behavior of the old [components],
  • the addition of [component] requires verification at the place of new use, and costs the programmer time
  • the change in behavior of the [component] caused by the new task requires verification at the place of new use and in all places of old use, which also causes the programmer’s time expenditure, and in the case of the published [component], the work of all programmers using the [component].
  • it is advisable to choose an option for implementing a new task while minimizing the time spent by the programmer.

    More often in the practice of software development, the cost of adding is much less than the cost of changing, which makes obvious the benefit of using the [Open-Closed] principle. At the same time, there are a lot of techniques for maintaining the program architecture in a state where the implementation of a new task comes down to adding only [components]. This work with architecture also requires a programmer’s time, but practice in large projects shows much less than using the approach of changing old procedures. And, of course, this description of the development is idealization. There is almost no implementation of the task by just adding or just changing. In real life, a mixture of these approaches is used, but OCP emphasizes the benefit of using the add approach.

    And here I would like to write out a set of techniques for maintaining OCP. From the obvious areas where you can look for these tricks:

  • design patterns;
  • dll libraries and options for their distribution, updating and development of functionality;
  • development of COM libraries and objects in them;
  • development of programming languages ​​and support for previously written code;
  • develop the legislative system of the state.

Liskov Substitution Principle (LSP) Barbara Liskov Substitution Principle

This principle limits the use of the extension of the base interface [base] to the implementation, stating that each implementation of the base interface should have behavior as a base interface. At the same time, the basic interface fixes the expected behavior in places of its use. And the presence in the implementation behavior of a difference from the expected behavior, fixed by the base interface, will lead to the possibility of violation of the invariant [2].

This principle is based and refines the design technique based on abstraction. In this approach, an abstraction is introduced - some basic properties and behavior characteristic of many situations are fixed. For example, [component-procedure] "Move to the previous position" for situations: "Cursor in text", "Book on a shelf", "Element in an array", "Feet in dance", etc. And assigned to this [component] ( often by everyday experience and without formalization) some prerequisites and behavior, for example: “The presence of a movable object”, “Repeat several times”, “Presence of the order of elements”, “Presence of fixed positions of elements”. LSP requires that when adding a new usage situation for [component] all the prerequisites and limitations of the base are met.

The purpose for this principle is to eliminate implicit errors introduced due to the following invariants in development:

  • [1], [2], [3] described previously,
  • the basic [procedure] describes a behavior that is useful in a large number of situations, setting the constraints required for its applicability,
  • the developed [procedure] for the implementation of the base must fulfill all its limitations, including hard-tracked implied ones (provided informally).

    Very often, an example with a Rectangle ([base]) and a Square (implementation) is given to describe this principle. The situation class CSquare : public CRectangle. In [base], operations for working with width and height (Set (Get) Width, Set (Get) Height) are introduced. In the implementation of CSquare, these Set operations are forced to change both sizes of the object. I always lacked the explanation that the following restriction is set "informally" in [the base]: "the ability to use Width, Height independently." In the implementation of CSquare, it is violated, and in places of use a simple sequence of actions based on the use of this independence: r.SetWidth(r.GetWidth()*2); r.SetHeight(r.GetHeight()*2)- for the implementation of CSquare, it will increase both sizes by 4 times, instead of 2 times assumed for CRectangle.

    IMHO this principle indicates the difficulty of tracking such informal constraints, which, with great utility and high frequency of use of the development approach "base-implementation" requires special attention.

Interface Segregation Principle (ISP) principle of separation of interfaces; Dependency Inversion Principle (DIP) Dependency Inversion Principle

These two principles are very close in the area of ​​their requirements. Both implicitly imply the usefulness of using the smallest possible basic interface as a tool for the interaction of two [components]: "client" and "server" - these names are chosen simply for identification. In this case, the general information used by the [components] is concentrated in the base interface. One [component] ("server") implements the implementation of the base interface, the other [component] ("client") refers to this implementation.

The goal for these principles is to minimize component dependencies, allowing independent changes to their code if it does not change the underlying interface. The independence of component changes reduces complexity and labor if components meet the requirements of the SRP principle. A similar approach is possible because the following invariants exist in development:

  • [1], [2], [3] described previously,
  • each [component] inherent in its behavior forms the limits of its use,
  • in each place of use of the [component] all its restrictions may be involved,
  • the basic [component] consequence of the definition has less complexity and the number of restrictions than the [component] implementation,
  • any change in [component] changes its limitations and requires verification of all places of its use, which causes a programmer’s time expenditure,
  • places of use of the base [component] do not require verification after making changes to the [component] implementation.

    It’s clear that it’s advisable to minimize the “size” of the base interface by discarding unused functionality and restrictions, thereby limiting [component] implementation by the principle of (LSP) less

    The principle of ISP emphasizes the need for separation (Segregation) of the interface of the "server", if not all of its published functionality is used by this "client". In this case, only the [base] required by the client is allocated and minimization of jointly restricting information is provided.

    And here I would like to write out a set of techniques for maintaining DIP. From the obvious areas where you can look for these tricks:

  • separation of the class description into public and private parts (and other principles of OOP),
  • description of interaction with a dynamic library with a limited set of functions and object descriptors,
  • using a file cabinet as an interface to access a book library.

Returning to the heading, I will explain why "not understand" is selected. Negation is added in order to emphasize by mistakes the long-suffering and very IMHO useful rule. It’s better not to understand and therefore not to use the technology, than to misunderstand, take it on faith, spend your resources on the application of the technology and as a result not get any useful exhaust except complacency and the possibility of boasting about being involved in fashionable technology.

Thanks for attention.


Also popular now: