Contribution of SOLID principle to low coupling and high cohesion pattern

Hadi Awad
Dev Genius
Published in
7 min readJul 17, 2020

--

In this reading we will be exploring the S.O.L.I.D principle and give a concrete example on each one of them and how they are related to attaining a high cohesive low coupled design.

Brief History and Introduction

SOLID principles are good to adopt, yet they are only a set of guidelines; which means reaching SOLID principles will not guarantee you a bug-free and charm working software, because principles are not facts nor rules, indeed they are common practices and solutions to common issues and difficulties.

The low coupling high cohesion was firstly recommended and introduced in 1960 as a design guidelines in structured programming and not OOP; at that time, they were considered as the building blocks for delivering an effective maintainable software, and applying them aims to achieve extensibility at some point of time. Those principles have proven that they can be applied to any technology and programming paradigm, such as Object-Oriented programming (OOP) and Aspect-Oriented Programming (AOP).

SOLID is a group of guidelines that were defined by Robert Martin who is well known as Uncle Bob. In general, SOLID is applied in software engineering to ensure Separation of Concern (SoC) in multi-component systems which in return implies the existence of low coupling high cohesion guidelines between system components. Basically, the objective of using them is to ensure the following,

  1. Reduce maintenance efforts.
  2. Minimize the complexity and cost of change.
  3. Enhance usability and extensibility.
  4. Improve application testability and code readability.

So what are cohesion and coupling?

The couplin gcan be defined as a measure to the degree of which one class is linked/dependent to/on other classes. Whereas, Cohesion is a measure to the degree of how much the elements composing the system are related to the service it offers and to each other as well. The rest of this document will briefly explain how we can accomplish high cohesive low coupled design through SOLID

S-Single Responsibility Principle

This also can be referred to as SRP, it simply means that there should be no more than one reason behind modifying a class i.e. every single class should have one responsibility at the max. So, in any condition, if you have several reasons to update a class then the class should be split up into several classes, in which each one is responsible for one functionality. Moreover, the whole responsibility and functionality should be encapsulated in that class.

This could be solved by taking out the format function from the Person and introducing a PersonFormatter Class that has this responsibility of formatting the objects of type person. Thus, the two classes are now decoupled and if any updates take place on any of them, the other class won’t be affected.

O-Open Closed Principle

Which was firstly defined by Dr. Bertrand Meyer in the year of 1988 and recommends that a software entity should be designed to be open for extension; meaning, that a behavior could be updated or new behavior added and in same time by inheritance and without changing the code of the entity, and that what is meant by closed, which means closed against any modification. By this we can guarantee that our class for example can take a new behavior without doing any piece of change. It’s worth noting here that decorator and observer are two design patterns which can be applied to achieve this principle.

The motive behind this principle is that the update of the class to add a new behavior would break the class main responsibility, so it’d be better if we add the new updates by extending the entity rather than changing it. We can accomplish this by using abstraction and polymorphism. Thus, behavior updates can be done by introducing new concrete classes or overriding methods. This will enhance the reusability and extensibility of our software entities.

By following the OCP, there will be no tight link between modules, the relation and dependence between entities is at the minimum since we only extended the already created modules and thus we achieved a low coupled design in which the flexibility and maintainability is high. Add to this, we preserved the SRP because adding new responsibilities will be extending the already existing behavior, hence, there would be less code changes, less unit-test breakages and regressions are avoided.

L-Liskov Substitution Principle

Which was proposed by Barabara Liskov in 1987, and states that in OO we can substitute every subclass with their superclass (parent) without breaking the application behavior. This guideline is considered to be a complementary to the OCP by ensuring that derived classes are extending the base classes without changing their behavior.

An example of this is a Rectangle class which is a shape of four right angles and has a width and a height,

Moving on, we want to define the Square, which is also a shape with four right angles but with 4 equal edges. Apparently, the Square is-a Rectangle, so we can simply make the Rectangle a superclass and square is a subclass, and write the following line of code

Now suppose that we have a unit-test in our application that was already defined for checking against the rectangle shape by examining the width and height of the figure and making sure that they are not equal. The question is, if we replaced the square we defined above, will it fail? The answer is yes, because the width and height of a square are equal and thus this hierarchy/inheritance breaks the LSP Rule.

LSP can also be simply explained in the following words, a developer should be able to replace any object with its superclass without breaking the correctness of his application. So, LSP helps in loose coupling by allowing us to use any instance that extends the superclass without the need to know what is the behavior of the replaced object and how it delivers its service.

I — Interface Segregation Principle

Which states that a client should not be dependent on an interface that it never uses, meaning never implement an interface which you do not use. That’s enforces a low coupled highly cohesive design by keeping the system entities focused as much as possible and minimizing the dependencies between them, right!

An example of the violation is the Worker interface which has the takeBreak() method, what if the object implementing that interface was a PartTimer? Then we are forced to implement an interface that is never used by those objects.

Designers can achieve the ISP by breaking the fat interfaces into smallest focused cohesive interfaces, in which every one of them aims to achieve a single responsibility and consequently achieving the SRP guidance as well. It’s OK if a class has implemented several interfaces, but designers must be aware not to break the SRP here. Enforcing the ISP and SRP here will allow your team to anticipate a change easily and can turn your application into a maintainable one.

D-Dependency Inversion Principle

DIP has two main principles,

  1. High level entities should not be dependent on the low level ones and indeed both of them should be dependent on abstraction
  2. Abstractions shouldn’t be dependent on details whereas details should be dependent on abstractions.

Looking at the above statements, we can observe that DIP recommends decoupling high level components from low ones and insert an abstraction layer between them. Moreover, it recommends inverting the dependency; meaning abstraction class should not be written based on details but details should be written based on the abstraction.

So if a Class A is dependent on B as illustrated in the below image, the DPI recommends the following,

  1. Introduce an Abstraction (Super Class) named B’
  2. Set up class A such that depends on Abstraction (Class B’)
  3. Introduce a class B that extends Class B’ and make Class A reference it as in the following diagrams

Generally in programming and development whenever a module needs some other dependency/entity, that module creates a direct reference to the dependency. Thus the two entities are now highly coupled. With the use of DIP we can break that link by letting the dependent-module provide us a hook, and then an external component which is managing the dependency will inject the needed dependency reference to the dependent-module. Hence, DIP offers the low coupling by just changing the dependency as needed via the external module. It’s worth noting here that the Factory method and Abstract Factory are both used along with specialized framework-called inversion of controller containers like Spring- to help in delivering the DI principle.

Conclusion

Having all that said, we can conclude that the key point is to avoid coupling. if we start fixing the design by breaking the tight coupling, we will implicitly turn our code into a better, maintainable and robust one. Following up the 5 SOLID principles is the first step, and trying to adhere to those guidelines turns our objective into an easy one to accomplish.

SOLID is not the only principle in software engineering to follow, there are many guidelines that can be useful and easy to adopt, all what we need to do is to start thinking about the design a little bit before rushing into coding.

--

--