SOLID: Software Design Principles. Be a better developer
Tags: solid, codequality, javascript, typescript
Published: 2020-09-26 22:57:28

**S.O.L.I.D** design principle comes from Object oriented programming guidelines. It is designed to develop software that can be easily maintained and extended; prevents code smells; easy to refractor; promotes agility and finally incorporates rapid + frequent changes quickly without bugs.

Generally, technical debt is the result of prioritizing speedy delivery over perfect code. To keep it under control - use SOLID principles, during development.

<a href="https://twitter.com/unclebobmartin" target="_blank">Robert Martin</a>, is credited with writing the SOLID principles and stated 4 major software issues if S.O.L.I.D is not followed diligently. They are :

- **<ins>Rigidity</ins>**:

  - Implementing even a small change is difficult since it's likely to translate into a cascade of changes.

- **<ins>Fragility</ins>**:

  - Any change tends to break the software in many places, even in areas not conceptually related to the change.

- **<ins>Immobility</ins>**:

  - We're unable to reuse modules from other projects or within the same project because those modules have lots of dependencies.

- **<ins>Viscosity</ins>**:
  - Difficult to implement new features the right way.

### SOLID is a guideline and not a rule. It is important to understand the crux of it and incorporate it with a crisp judgement. There can be a case when only few principles out of all is required.

## <ins>S.O.L.I.D stands for</ins>:

- **Single Responsibility Principle (SRP)**;
- **Open Closed Principle (OCP)**;
- **Liskov Substitution Principle (LSP)**;
- **Interface Segregation Principle (ISP)**;
- **Dependency Inversion Principle (DIP)**;


## <ins>**Single Responsibility Principle (SRP)**</ins>

Every function, class or module should have one, and only one reason to change, implies should have only one job and encapsulate within the class (stronger cohesion within the class).

`It supports "Separation of concerns" — do one thing, and do it well!"`

**Sidenote: Originally this article is published on [Box Piper](https://www.boxpiper.com/posts/solid-software-design-principles/) source.**

For example, consider this class:

```typescript
class Menu {
  constructor(dish: string) {}
  getDishName() {}
  saveDish(a: Dish) {}
}
```

This class violates SRP. Here is why. It is managing the properties of menu and also handling the database. If there is any update in database management functions then it will affect the properties management functions as well, hence resulting in coupling.

More cohesive and Less Coupled class Instance.

```typescript
// Responsible for menu management
class Menu {
  constructor(dish: string) {}
  getDishName() {}
}

// Responsible for Menu management
class MenuDB {
  getDishes(a: Dish) {}
  saveDishes(a: Dish) {}
}
```

## <ins>**Open Closed Principle (OCP)**</ins>

Classes, functions, or modules should be opened for extensibility, but closed for modification.
If you created and published a class - changes in this class, it can break the implementation of those, who are started using this class. Abstraction is the key to getting OCP right.

For example, consider this class:

```typescript
class Menu {
  constructor(dish: string) {}
  getDishName() {}
}
```

We want to iterate through a list of dishes and return their cuisine.

```typescript
class Menu {
constructor(dish: string){ }
getDishName() { // ... }

    getCuisines(dishName) {
      for(let index = 0; index <= dishName.length; index++) {
         if(dishName[index].name === "Burrito") {
            console.log("Mexican");
         }
         else if(dishName[index].name === "Pizza") {
            console.log("Italian");
         }
      }
    }

}
```

The function getCuisines() does not meet the open-closed principle because it cannot be closed against new kind of dishes.

If we add a new dish say `Croissant`, we need to change the function and add the new code like this.

```typescript
class Menu {
constructor(dish: string){ }
getDishName() { // ... }

    getCuisines(dishName) {
      for(let index = 0; index <= dishName.length; index++) {
         if(dishName[index].name === "Burrito") {
            console.log("Mexican");
         }
         if(dishName[index].name === "Pizza") {
            console.log("Italian");
         }
         if(dishName[index].name === "Croissant") {
            console.log("French");
         }
      }
    }

}
```

If you observe, for every new dish, a new logic is added to the getCuisines() function. As per the open-closed principle, the function should be open for extension, not modification.

Here is how we can make the codebase meets the standard to OCP.

```typescript
class Menu {
  constructor(dish: string) {}
  getCuisines() {}
}

class Burrito extends Menu {
  getCuisine() {
    return "Mexican";
  }
}

class Pizza extends Menu {
  getCuisine() {
    return "Italian";
  }
}

class Croissant extends Menu {
  getCuisine() {
    return "French";
  }
}

function getCuisines(a: Array<dishes>) {
  for (let index = 0; index <= a.length; index++) {
    console.log(a[index].getCuisine());
  }
}

getCuisines(dishes);
```

This way we do not need to modify the code whenever a new dish is required to add. We can just create a class and extends it with the base class.

## <ins>**Liskov Substitution Principle (LSP)**</ins>

A sub-class must be substitutable for their base type, states that we can substitute a subclass for its base class without affecting behaviour and hence helps us conform to the "is-a" relationship.

In other words, subclasses must fulfil a contract defined by the base class. In this sense, it's related to Design by Contract that was first described by <a href="https://twitter.com/bertrand_meyer" target="_blank"> Bertrand Meyer </a>.

For example, Menu has a function `getCuisines` which is used by Burrito, Pizza, Croissant and didn't created individual functions.

```typescript
class Menu {
  constructor(dish: string) {}
  getCuisines(cuisineName: string) {
    return cuisineName;
  }
}

class Burrito extends Menu {
  constructor(cuisineName: string) {
    super();
    this.cuisine = cuisineName;
  }
}

class Pizza extends Menu {
  constructor(cuisineName: string) {
    super();
    this.cuisine = cuisineName;
  }
}

class Croissant extends Menu {
  constructor(cuisineName: string) {
    super();
    this.cuisine = cuisineName;
  }
}

const burrito = new Burrito();
const pizza = new Pizza();
burrito.getCuisines(burrito.cuisine);
pizza.getCuisines(pizza.cuisine);
```

## <ins>**Interface Segregation Principle (ISP)**</ins>

A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.

The "interface" word in the principle name does not strictly mean an interface, it could be an abstract class .

For example

```typescript
interface ICuisines {
  mexican();
  italian();
  french();
}

class Burrito implements ICuisines {
  mexican() {}
  italian() {}
  french() {}
}
```

If we add a new method in the interface, all the other classes must declare that method or error will be thrown.

To solve it

```typescript
interface BurritoCuisine {
  mexican();
}
interface PizzaCuisine {
  italian();
}

class Burrito implements BurritoCuisine {
  mexican();
}
```

Many client-specific interfaces are better than one general-purpose interface.

## <ins>**Dependency Inversion Principle (DIP)**</ins>

Entities must depend on abstractions not on concretions. It states that the high level module must not depend on the low level module, decouple them and make use of abstractions.

High-level modules are part of an application that solve real problems and use cases.
They are more abstract and map to the business domain (business logic);
They tell us what the software should do (not how, but what);

Low-level modules contain implementation details that are required to execute the business policies; About how the software should do various tasks;

For Example

```typescript
const pool = mysql.createPool({});
class MenuDB {
  constructor(private db: pool) {}
  saveDishes() {
    this.db.save();
  }
}
```

Here, class MenuDB is a high-level component whereas a pool variable is a low-level component. To solve it, we can separate Connection instance.

```typescript
interface Connection {
  mysql.createPool({})
}

class MenuDB {
   constructor(private db: Connection) {}
   saveDishes() {
      this.db.save();
   }
}
```

## <ins>Ending Note</ins>

Code that follows S.O.L.I.D. principles can be easily shared, extended, modified, tested, and refactored without any problems. With each real-world application of these principles benefits of the guidelines will become more apparent.

Anti-patterns and improper understanding can lead to **STUPID** code: Singleton, Tight Coupling, Un-testability, Premature Optimization, In-descriptive Naming, and Duplication. SOLID can help developers stay clear of these.


To read more such interesting topics, follow and read <a href="https://boxpiper.com/">BoxPiper blog</a>.

Support my work and [buy me a Coffee](https://ko-fi.com/E1E72C2MM). It'll mean the world to me. 😇

<a href="https://ko-fi.com/E1E72C2MM"><img src="https://cdn.ko-fi.com/cdn/kofi1.png?v=2"/></a>