Skip to content

SOLID

Introduction

SOLID is a set of software design principles that every good programmer should know and follow when creating code.

SOLID is an acronym and the letters stand for:

  • Single Responsibility Principle
  • Open Closed Principle
  • Liskov's Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Single Responsibility Principle

The first rule is that each class should have one and only one role in the system. For example, if a class describes a model from the database, then it should not additionally define methods that will allow you to retrieve data from the database. This limitation of class responsibilities makes the code more flexible. Changing a class that does "little" is relatively simple and reduces the need for changes elsewhere in the system. A change to a class or method that has, for example, a few hundred lines of code, forces more tests, more changes, and more time to understand the code.

Example

Without applying the Single Responsibility rule:

class Book:
    def __init__(self, book_id, title, author, no_of_pages):
        self._book_id = book_id
        self._title = title
        self._author = author
        self._no_of_pages = no_of_pages

    def SearchBookByTitle(self, title):
        pass

    def SearchBookByAuthor(self, author):
        pass

Apart from describing the book model, the above class also tries to make them searchable. For this purpose, it is better to separate a separate class:

class Book:
    def __init__(self, book_id, title, author, no_of_pages):
        self._book_id = book_id
        self._title = title
        self._author = author
        self._no_of_pages = no_of_pages
class BookSearchEngine:
    def SearchBookByTitle(self, title):
        pass

    def SearchBookByAuthor(self, author):
        pass

The limitation of liability does not mean that a class or interface should only define one method. The important thing is not to confuse class responsibilities. For example, a search engine may have separate methods that search for records by a specific key, but we should implement a filtering or sorting class separately.

Open Closed Principle

Classes should be open for extension but closed for modification. This means that when creating code, we should write it in such a way that no changes to classes not related to the added functionality are required.

An example of use can be an example for a pattern Strategy. Imagine you want to extend the functionality of your program so that the end user in the input string can replace spaces with the- character. To do this, just add another implementation of the SpacesModificationStrategy interface. The structure of the program does not require any changes.

Liskov's Substitution Principle

The Liskov substitution principle was first formulated by Barbara Liskov and says that by using a base type, you can substitute any type of a derived class (i.e., inheriting from it) and keep the functionality working. This principle is especially widely used in Python, which uses duck typing, which allows the underlying interfaces to be freely used.

Interface Segregation Principle

The principle of Interface Segregation is to undefine interfaces that have too many methods, in the sense that their implementations only overwrite some of those methods (and undefined are never used). In order to solve such a problem (if we have already encountered one), it is better to divide the interface into several smaller ones, in such a way that each of them performs a specific function. Thanks to this, the dependency tree between objects in the application will be simplified.

Dependency Inversion Principle

The Dependency Inversion rule is one of the most important rules when creating code. It consists in the fact that a given class, wanting to use certain dependent objects, should receive references of these objects "from the outside", but should not be responsible for their creation. With this approach, the class can run on abstractions (i.e., abstract class, base class, or interface) instead of a specific implementation (i.e., additionally making use of Liskov's Substitution Principle).

This rule was used, for example, in the Chain of responsibility in class ChainAuthenticationElement, which takes dependencies in the constructor AuthenticationHandler and ChainAuthenticationElement (rather than directly implementing them)