Solid Principles
Published: 2023-12-05 13:20:10

## What is SOLID?
SOLID is an acronym for five software design principles introduced by Robert C. Martin in the 2000s, that aim to help us structure our code in order to:

- Tolerate change.
- Ease code understanding.
- Write components that can be used in many software systems.

### SOLID stands for:

- **S**: Single Responsibility Principle
- **O**: Open-Closed Principle
- **L**: Liskov Substitution Principle
- **I**: Interface Segregation Principle
- **D**: Dependency Inversion Principle

---

## **S - Single Responsibility**

![Single Responsibility](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cmdn39u4eguq9sjnxj7w.png)

A Class should have one, and only one reason to change.

If our classes assume **multiple responsibilities**, they will be **highly coupled thus making them more difficult to maintain**.

### What’s a reason to change?
Uncle Bob states that this principle is about people. This means that when you write a software module, and changes are requested on that module, those changes can only originate from a single person or a tightly group of people representing a single narrowly defined business function.

Another definition for this principle is:

Gather together those things that change for the same reason, and separate those things that change for different reasons.

This can also be considered the definition of [Separation of Concerns](https://en.wikipedia.org/wiki/Separation_of_concerns).

The following piece of code shows a violation of the SRP in which the book class is both a representation of an entity and also implements the persistence of such an entity.

### (S) - Wrong Approach

```js
class Book {
  constructor(private _author: string, private _title: string) {}

  get author(): string {
    return this._author;
  }

  get title(): string {
    return this._title;
  }

  save(): void {
    // Save book in the database.
  }
}

```

### (S) - Better Approach

```js
class Book {
  constructor(private _author: string, private _title: string) {}

  get author(): string {
    return this._author;
  }

  get title(): string {
    return this._title;
  }
}

interface RepositoryInterface<T> {
  save(entity: T): void;
}

class BookRepository<T extends Book> implements RepositoryInterface<T> {
  save(book: Book): void {
    // Save book in the database
  }
}

```

---

## **O - Open for Extension - Closed for Modification**

![Open-Closed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xm984wgpf857g8xm60ri.png)

Software entities should be open for extension but closed for modification.

This principle states that software entities must be extensible without modifying the existing code. In order to achieve this, we need to make abstractions. By doing this, we’ll be able to extend the behavior of a class without changing a single line of code.

### (O) - Wrong Approach

```js
class Rectangle {
  constructor(private _width: number, private _height: number) {}

  get height(): number {
    return this._height; 
  }

  get width(): number {
    return this._width;
  }
}

class Square {
  constructor(private _height: number) {}

  get height(): number {
    return this._height; 
  }
}

class AreaCalculator {
  private shapes: any[];

  constructor(shapes: any[]) {
    this.shapes = shapes;
  }

  public sum() {
    return this.shapes.reduce((acc, shape) => {
      if (shape instanceof Square) {
        acc += Math.pow(shape.height, 2);
      }
      if (shape instanceof Rectangle) {
        acc += shape.height * shape.width;
      }
      return acc;
    }, 0);
  }
}

```

### (O) - Better Approach

```js
interface Shape {
  area() : number;
}

class Rectangle implements Shape {
  constructor(private _width: number, private _height: number) {}

  public area() : number {
    return this._height * this._width;
  }
}

export class Square implements Shape {
  constructor(private _height: number) {}

  public area() : number {
    return Math.pow(this._height, 2);
  }
}

class AreaCalculator {
  private shapes: Shape[];

  constructor(shapes: Shape[]) {
    this.shapes = shapes;
  }

  public sum() : number {
    return this.shapes
      .reduce((acc, shape) => acc += shape.area(), 0);
  }
}

```

---

## **L - Liskov Substitution Principle**

![Liskov Substitution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/83fp0pa4k5hcmcpm77mw.png)

Inherit from a Parent Class but without modifying the parent’s method behavior.

This principle states that objects must be **replaceable by instances of their subtypes without altering the correct functioning of the system**.

A classic example of a violation of this principle is the [Rectangle-Square problem](https://www.blackwasp.co.uk/SquareRectangle.aspx). The Square class extends the Rectangle class and assumes that the width and height are equal. When calculating the area of a square, we’d get the wrong value.

### (L) - Wrong Approach

```js
class Rectangle {
  constructor(private _width: number, private _height: number) {}

  public area() : number {
    return this._height * this._width;
  }
}

class Square extends Rectangle {}

```

### (L) - Better Approach

```js
interface Shape {
  area() : number;
}

class Rectangle implements Shape {
  constructor(private _width: number, private _height: number) {}

  public area() : number {
    return this._height * this._width;
  }
}

class Square implements Shape {
  constructor(private _height: number) {}

  public area() : number {
    return Math.pow(this._height, 2);
  }
}

```

---

## **I - Interface Segregation**

![Interface Segregation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ixte4sqmsr8be2qjma28.png)

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

This principle states that classes **should never implement interfaces that they don’t need to use**. If they do, we’ll end up having **not implemented methods** in our classes. This can be solved by **creating specific interfaces instead of general-purpose interfaces**.

### (I) - Wrong Approach

```js
interface VehicleInterface {
  drive(): string;
  fly(): string;
}

class FutureCar implements VehicleInterface {
  public drive() : string {
    return 'Driving Car.';
  }

  public fly() : string {
    return 'Flying Car.';
  }
}

class Car implements VehicleInterface {
  public drive() : string {
    return 'Driving Car.';
  }

  public fly() : string {
    throw new Error('Not implemented method.');
  }
}

class Airplane implements VehicleInterface {
  public drive() : string {
    throw new Error('Not implemented method.');
  }
  
  public fly() : string {
    return 'Flying Airplane.';
  }
}

```

### (I) - Better Approach

```js
interface CarInterface {
  drive() : string;
}

interface AirplaneInterface {
  fly() : string;
}

class FutureCar implements CarInterface, AirplaneInterface {
  public drive() {
      return 'Driving Car.';
  }

  public fly() {
      return 'Flying Car.'
  }
}

class Car implements CarInterface {
  public drive() {
      return 'Driving Car.';
  }
}

class Airplane implements AirplaneInterface {
  public fly() {
      return 'Flying Airplane.';
  }
}

```

---

## **D - Dependency Inversion**

![Dependency Inversion](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5mrac9ego3pc0atp4rpk.png)

_Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions._

This principle states that a class should not depend on another class, but instead on an abstraction of that class. It allows loose coupling and more reusability.

### (D) - Wrong Approach

```js
class MemoryStorage {
  private storage: any[];

  constructor() {
    this.storage = [];
  }

  public insert(record: any): void {
    this.storage.push(record);
  }
}

class PostService {
  private db = new MemoryStorage();

  createPost(title: string) {
    this.db.insert(title);
  }
}

```

### (D) - Better Approach

```js
interface DatabaseStorage {
  insert(record: any): void;
}

class MemoryStorage implements DatabaseStorage {
  private storage: any[];

  constructor() {
    this.storage = [];
  }

  public insert(record: any): void {
    this.storage.push(record);
  }
}

class PostService {
  private db: DatabaseStorage;

  constructor(db: DatabaseStorage) {
    this.db = db;
  }

  createPost(title: string) {
    this.db.insert(title);
  }
}

```