Introdução ao SOLID - Princípios L e I
Tags: beginners, oop, typescript, braziliandevs
Published: 2022-06-20 20:50:31

## Tabela de Conteúdos

- [Liskov Substitution Principle](#liskov-substitution-principle)
- [Interface Segregation Principle](#interface-segregation-principle)
- [Links Úteis](#links-úteis)

---

## Liskov Substitution Principle

### Recomendação

O Princípio de Substituição de Liskov diz que devemos poder utilizar uma sub-classe, no lugar de uma super-classe. Na prática isso ocorre através da implementação de Interfaces, ou através da herança de classes, dessa forma toda a sub-classe que implementa determinada interface, ou herda de determinada super-classe, deve poder ser usada como substituto.

### Exemplo

A forma mais fácil de se entender o Princípio de Substituição de Liskov é através de classes que fazem a conexão com o banco de dados, dessa forma podemos ter diversas classes, responsáveis por diversos bancos, podendo se substituírem sem a geração de efeitos colaterais _(bugs)_.

Pensando no contexto acima, vamos exemplificar a criação de um usuário em dois DB diferentes, o MySQL e o MongoDB. Para isso iremos criar duas classes que implementam uma mesma Interface e utilizá-las da mesma forma.

``` ts
interface IUserRepository {
  create(name: string, age: number): Promise<void>;
}
```

``` ts
import mysql from 'mysql2/promise';  // Esse pacote foi utilizado apenas como exemplo

class UserRepositoryMySQL implements IUserRepository {
  constructor() {
    this.mysql = mysql.createPool({
      /* configuração da conexão */
    });
  }

    async create(name: string, age: number): Promise<void> {
      await this.mysql.execute(
        'INSERT INTO users (name, age) VALUES (?, ?);',
        [name, age]
      )
    }
  }
```

``` ts
import { User } from 'mongooseModels';  // pasta "fictícia"que armazena as Models do Mongoose

class UserRepositoryMongo implements IUserRepository {
  constructor() {
    this.userModel = new User();
  }

  async create(name: string, age: number): Promise<void> {
    await this.userMode.create({ name, age });
  }
}
```

``` ts
import { IUserRepository } from 'IUserRepository';

class CreateUSerService {
  constructor(private userRepository: IUserRepository) {}

  async execute(name: string, age: number) {
    this.userRepository.create(name, age);
  }
}
```

``` ts
import { Request, Response, NextFunction } from 'express';

import { UserRepositoryMySQL } from 'UserRepositoryMySQL';
import { UserRepositoryMongo } from 'UserRepositoryMongo';

import { CreateUserService } from 'CreateUserService';

const userRepositoryMySQL = new UserRepositoryMySQL();
const userRepositoryMongo = new UserRepositoryMongo();

/* ---------- Criando usuário no MySQL ---------- */
const createUserService = new CreateUserService(userRepositoryMySQL);
/* ---------- ----------------------- ---------- */

/* ---------- Criando usuário no MongoDB ---------- */
const createUserService = new CreateUserService(userRepositoryMongo);
/* ---------- ------------------------- ---------- */

const createUserRoute = async (req: Request, res: Response, next: NextFunction) => {
  const { name, age } = req.body;

  try {
    await createUserService.execute(name, age);
    res.status(200).end();
  } catch {
    res.status(500).json({ message: 'Internal server error' });
  }
}
```

No exemplo acima criamos duas classes que lidam com DB diferentes, porém podemos utilizar qualquer uma das duas em nosso serviço de criação de usuário _`CreateUSerService`_, isso porque o serviço espera a Interface `IUserRepository` e como nossas classes implementam essa Interface podem ser usadas como substitutas.

[Voltar ao top](#tabela-de-conteúdos)

---

## Interface Segregation Principle

### Recomendação

O Princípio de Segregação de Interfaces recomenda que separemos nossas Interfaces em _"blocos mínimos"_, em outras palavras, as criemos altamente especializadas e caso surja a necessidade, podemos criar uma Interface mais completa estendendo as mais específicas.

### Exemplo

O Princípio de Segregação de Interfaces é, na minha opinião, o mais simples de se entender o conceito teórico, porém o mais difícil de se aplicar em um caso real.

Nosso exemplo será uma classe de serviço de uma API, ele deverá obrigatoriamente possuir o método `execute` para executar sua tarefa e opcionalmente poderá possuir métodos de validação, como por exemplo: _validar se um email já está em uso_.

``` ts
// Iremos utilizar generics para "tipar" o input <T> e o output <O> posteriormente

interface IServiceExecute<T, O> {
  execute(T): Promise<O>;
}

interface IServiceValidUnique<T> {
  isUnique(T): Promise<boolean>;
}
```

``` ts
interface IRequest {
  username: string;
  email: string;
  password: string;
}

interface ICreatedUser extends IRequest {
  id: string;
}

type UniqueUser = Pick<IRequest, 'email'>

class RegisterUserService implements IServiceExecute<IRequest | null, ICreateUser>, IServiceValidUnique<UniqueUser> {
  constructor(private repository: IRepository) {}

  async isUnique({ email }: UniqueUSer): Promise<boolean> {
    const emailAlreadyInUse = await this.repository.find(email);

    if (emailAlreadyInUse) {
      return false;
    }

    return true;
  }

  async execute({ username, email, password }: IRequest): Promise<ICreatedUser> {
    const isUnique = await this.isUnique({ email });

    if (!isUnique) {
      return null;
    }

    const newUSer = await this.repository.create({ username, email, password });
    return newUser;
  }
}
```

No exemplo acima estamos aplicando duas Interfaces em uma única classe, isso porque cada Interface é responsável apenas por uma funcionalidade, dessa forma se precisarmos construir um serviço que não precise de validação, podemos apenas implementar a `IServiceExecute`.

Obs: As Interfaces criadas junto da classe são um _"complemento"_ as outras, isso porque optei por utilizar Generics na criação das Interfaces de serviço, logo é necessário inferir seu tipo posteriormente através de tipos primitivos, `types` ou `interfaces`.

[Voltar ao top](#tabela-de-conteúdos)

---

## Links Úteis

- [Princípios SOLID com TypeScript](https://medium.com/@matheusbessa_44838/princ%C3%ADpios-solid-com-typescript-4f8a9d5d1ef8) por [Matheus Bessa](https://medium.com/@matheusbessa_44838)
- [Os Princípios do SOLID — ISP Princípio de segregação da interface](https://medium.com/xp-inc/os-princ%C3%ADpios-do-solid-isp-princ%C3%ADpio-de-segrega%C3%A7%C3%A3o-da-interface-1822ebc802fd) por [Jones Roberto](https://medium.com/@jonesroberto)
- [Os Princípios do SOLID — LSP -Princípio da substituição de Liskov](https://medium.com/xp-inc/os-princ%C3%ADpios-do-solid-lsp-princ%C3%ADpio-da-substitui%C3%A7%C3%A3o-de-liskov-35bcff93cd86) por [Jones Roberto](https://medium.com/@jonesroberto)

[Voltar ao top](#tabela-de-conteúdos)