S.O.L.I.D. Principles Revisited

In Software Development, Object Oriented programming concepts and design principles play a prime role to come up with scalable, flexible, maintainable, and reusable code. S.O.L.I.D. is a mnemonic acronym for the five principles intended to make software designs more understandable, flexible, and maintainable. The principles are a subset of many principles promoted by American software engineer and instructor Robert C. Martin, first introduced in his 2000 paper Design Principles and Design Patterns.

The application of the principles is universal and is not in relation to any specific programming language.

Let’s have a closure look at each of the principles. Following the S.O.L.I.D. acronym, they are:

  • The Single Responsibility Principle
  • The Open-Closed Principle
  • The Liskov Substitution Principle
  • The Interface Segregation Principle
  • The Dependency Inversion Principle

The Single Responsibility Principle

The Single Responsibility Principle states that every class, function, or module should have responsibility for a single part of that program’s functionality, and it should encapsulate that part. Robert C. Martin, the originator of the term, expresses the principle as, “A class should have only one reason to change”.

Here in the example, the responsibility of publishing the article is moved to a new class ‘Publisher’. Not only have we developed a class ‘Publisher’ that relieved the ‘Article’ class of its publishing responsibility, but we can also leverage the ‘Publisher’ class to further extend the publishing to different media. Now we have a separate class dedicated to this one concern.

The Open-Closed Principle

The Open-Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”; that is, such an entity can allow its behavior to be extended without modifying its source code. To put it simply, code should be capable of accommodating the additions, without any modifications required to the existing functionality.

Here in the example below, earlier the responsibility of the perimeter calculation for individual shapes was with the ‘Shape’ class itself. And because of that any addition or change in shapes required change in the ‘Shape’ class as well. To address the same, the responsibility of computing the perimeter for individual shapes is moved to respective shapes, and the ‘Shape’ class is computing the total perimeter based on the values provided.

So here the solution is open for extension — can add any number of new shapes, but closed for modification — ‘Shape’ class doesn’t require to change for any new shapes added.

The Liskov Substitution Principle

The Liskov Substitution Principle states that the functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it. For instance, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., an object of type T may be substituted with any object of a subtype S) without altering any of the desirable properties of the program.

Here in the example above, the ‘VolumeCalculator’ class is a subclass of the ‘AreaCalculator’ class and can be substitutable for its base class. This is the expected behavior because when we use inheritance, we assume that the child class inherits everything that the superclass has. Below is the code snippet to demonstrate the similar behavior –

AreaCalculator areaCalculator = new AreaCalculator(normalShape);VolumeCalculator volumeCalculator = new VolumeCalculator(shape3D);AreaVolumePrinter areaVolumePrinter = new AreaVolumePrinter();// q(x)areaVolumePrinter.printAreaVolume(areaCalculator);// q(y)areaVolumePrinter.printAreaVolume(volumeCalculator);

The Interface Segregation Principle

The Interface Segregation Principle states that no code, application, or client should be forced to depend on methods it does not use. The principle further suggests dividing the interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them. Such smaller/divided interfaces are also known as Role Interfaces. The principle is intended to keep a system decoupled and thus easier to refactor, change, and redeploy.

Here in the example, the interface segregation between the ‘Animal’ class and the ‘PetAnimal’ class, made the model more flexible, expendable, and the clients (the ‘Lion’ class in this case) don’t need to implement any irrelevant methods (‘FavPetFood’ in this case).

The Dependency Inversion Principle

The Dependency Inversion Principle is a specific form of loosely coupling software modules. Aligning to the principle, the modules or classes should depend upon abstractions, not concretions. Further principle states –

  • High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g., interfaces).
  • Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

Here in the example, the ‘DBConnection’ interface has the ‘connect’ method, and different classes have implemented this interface for connection to specific databases. Further to have the abstraction at the class ‘ArticleStorageManager’, the same abstraction is referenced instead of binding it with concrete classes (e.g. the ‘OracleDBConnection’ class or the ‘MySQLDBConnection’ class).

S.O.L.I.D. Principles — GIT Reference

The code in reference to the examples/UML diagrams used in this article can be accessed from the GIT Repository — https://github.com/vikasg11/solid-principles

Further Reading / References



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store