Photo by Markus Spiske on Unsplash

Clean Architecture -> PART III: Design Principles (SOLID)

Muhammed Ali Aydın

--

A good software system is provided by clean code. When bricks are not good, no matter how good the developed architecture is. On the other hand, a pile of rubbish made of very good bricks also makes no sense. This is where SOLID principles come into play.

SOLID principles determine how functions and data structures are organized in classes and how these classes relate to each other.

The purpose of these principles in creating a mid-level software structure is;

● Changes to be appropriate

● Easy to understand

● Ability to use basic components used in most software systems

Chapter 7: SRP: The Single Responsibility Principle

In its simplest definition, as the name implies, each module is based on the principle that it should be responsible only for one thing. The book is described by the following words:” A module should be responsible to one, and only one, actor.”

1. Symptom 1: Accidental Duplication

Suppose the Employee class of a salary payment application is issued. This class has three methods: calculatePay(), reportHours(), and save()

calculatePay(): Used by CFO (Accounting Department)

● reportHours(): Used by COO (Human Resources)

save(): Used by CTO (Database Administrators)

The Employee class

It can be said that this class violates the SRP principle because it is used by three different actors. The fact that the developer puts these three methods into only one Employee class causes the actors to be interdependent.

Suppose that the calculatePay() and reportHours() functions use a common algorithm to calculate non-overtime hours. Let's assume that the developer gives these algorithms in a single regularHours() function to avoid duplicate code generation.

Shared algorithm

The CFO team would like to make a small change in the calculation of non-overtime hours, while the COO team expects these hours to continue to be calculated as before. The function is changed as the CFO team wants, but the COO team continues to use it unaware that it has been changed. For this reason, it constantly deals with wrong values, causing millions of dollars of budget damage due to incorrect data.

The reason for all these problems stems from the fact that different actors are very closely connected. The SRP principle explains this: “Codes to which different actors are dependent should be separated.”

2. Symptom 2: Accidental Duplication

Suppose that two different developers or developer teams are trying to change the Employee class simultaneously. In such a case there will be a code conflict and this will result in merge problems.

Although tools have been developed to solve merge problems today, there is no tool to deal with every merge case. Therefore, there is always risk.

This problem can be solved in different ways. However, in all, functions must be moved out of class.

The “Facade” pattern

Facade patterns are generally used to solve such problems, as shown in the figure above. The specified functions are divided into three different classes and communicate only with an Employee Data class with data structures. Since these three classes do not know about each other, accidental duplication can be prevented in this way. There is very little code in the corresponding EmployeeFecade class. This class is only responsible for instantiating and delegating.

Chapter 8: OCP: The Open-Closed Principle

The general idea of this principle is great. It tells you to write your code so that you will be able to add new functionality without changing the existing code. That prevents situations in which a change to one of your classes also requires you to adapt all depending classes. Unfortunately, Bertrand Mayer proposes to use inheritance to achieve this goal: “A class is closed, since it may be compiled, stored in a library, baselined, and used by client classes. But it is also open, since any new class may use it as parent, adding new features. When a descendant class is defined, there is no need to change the original or to disturb its clients.”. The nature of a software must be extensible without any modification.

The main benefit of this approach is that an interface introduces an additional level of abstraction which enables loose coupling. The implementations of an interface are independent of each other and don’t need to share any code. If you consider it beneficial that two implementations of an interface share some code, you can either use inheritance or composition.

Chapter 9: LSP: The Liskov Substitution Principle

1. Guiding The Use of Inheritance

License, and its derivatives, conform to LSP

Suppose we have a class called License and let it have a calcFee() function used to calculate the bill. This License class also has two subclasses, PersonalLicense and BusinessLicense. These two classes use different algorithms to calculate the license fee.

When the solution to this problem is handled according to LSP, Billing’s behavior can easily work through the License interface, without any difference to any subclass type.

As a result, LSP extends the level of architecture. The slightest substitution violation causes the system architecture to be contaminated with extra mechanisms.

Chapter 10: ISP: The Interface Segregation Principle

The Interface Segregation Principle

Suppose that more than one user uses the functions of the OPS class. However, User1 only uses op1, User2 uses only op2 and User only uses op3.

When the code for the corresponding state is written in OO language, User1 will become dependent, even if it does not call them with User2 and User3 for no reason. This dependency will also require that a change in op2 or op3 be recompiled and deployed, even if no changes have been made to User1.

This problem can be solved by separating operations into interfaces as follows. By converting these operations to the interface, the dependencies between the Users will be eliminated, so that any action performed on the others will not affect unrelated Users.

Segregated operations

1. ISP and Language

The description mentioned in the previous section shows critical changes depending on the type of language.

In statically typed languages (such as Java), users must use declaration, such as import, use, or include. Especially in the include declarations in the source code, the source code dependencies are forced to be recompiled and deployed.

In dynamically typed languages (such as Ruby and Python), most declarations are not included in the source code. Instead it is called at run time. Therefore, source code dependencies are not forced to compile and deploy again. This is the main reason why dynamically typed languages create more flexible and lighter connected systems than statically typed languages.

It is worth noting that the ISP is a language problem rather than an architectural problem.

Chapter 11: DIP: The Dependency Inversion Principle

The general idea of this principle is as simple as it is important: High-level modules, which provide complex logic, should be easily reusable and unaffected by changes in low-level modules, which provide utility features. To achieve that, you need to introduce an abstraction that decouples the high-level and low-level modules from each other.

Based on this idea, Robert C. Martin’s definition of the Dependency Inversion Principle consists of two parts:

● High-level modules should not depend on low-level modules. Both should depend on abstractions.

● Abstractions should not depend on details. Details should depend on abstractions

An important detail of this definition is, that high-level and low-level modules depend on the abstraction. The design principle does not just change the direction of the dependency, as you might have expected when you read its name for the first time. It splits the dependency between the high-level and low-level modules by introducing an abstraction between them. So in the end, you get two dependencies:

  1. the high-level module depends on the abstraction, and
  2. the low-level depends on the same abstraction

--

--