Era SOLID o que me faltava
Tags: programming, beginners, productivity, braziliandevs
Published: 2024-01-02 22:16:37

Começou agora na **programação orientada a objetos** e não sabe sobre **SOLID**? Não se preocupe, nesse artigo vou te explicar e dar exemplos de como usá-lo no desenvolvimento do seu código. 

- [O que é SOLID?](#o-que-é-solid)
- [S - Princípio da responsabilidade única](#s-princípio-da-responsabilidade-única)
- [O - Princípio Aberto-Fechado](#o-princípio-aberto-fechado)
- [L - Princípio da substituição de Liskov](#l-princípio-da-substituição-de-liskov)
- [I - Princípio da Segregação da Interface](#i-princípio-da-segregação-da-interface)
- [D - Princípio da inversão da dependência](#d-princípio-da-inversão-da-dependência)
- [Conclusão](#conclusão)

## O que é SOLID?

Na programação orientada a objetos, o termo **SOLID** é um [acrônimo](https://pt.wikipedia.org/wiki/Acr%C3%B3nimo "Acrônimo") para cinco postulados de design, destinados a facilitar a compreensão, o desenvolvimento e a manutenção de software.

Ao usar esse conjunto de princípios é notável a redução na produção de bugs, melhora na qualidade do código, produção de códigos mais organizados, na redução de acoplamento, na melhora da refatoração e estimula o reaproveitamento do código.

## S - Princípio da responsabilidade única

![exemplo Princípio da responsabilidade única](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oebel9z9m0pupvh0bg02.png)

>*SRP - Single Responsibility Principle*

Esse principio diz que **uma classe deve ter um, e somente um, motivo para mudar**

É isso ai, nada de fazer classes que tenha varias funcionalidades e responsabilidades. Provavelmente você já fez ou se deparou com alguma classe que faz de tudo um pouco, a tal da *God Class*. Pode parecer que está tudo bem naquele momento, porem quando for necessário fazer alguma alteração na logica dessa classe é certeza que os problemas vão começar a aparecer.

>**God Class - Classe Deus:** Na programação orientada a objetos, é uma classe que sabe demais ou faz demais.

```typescript
class Task {
	createTask(){/*...*/}
	updateTask(){/*...*/}
	deleteTask(){/*...*/}

	showAllTasks(){/*...*/}
	
	existsTask(){/*...*/}
	
	TaskCompleter(){/*...*/}
}
```
Essa classe **Task** esta quebrando o princípio do **SRP** por esta fazendo **QUATRO** tarefas diferentes. Ela está lidando com os dados, exibição, validação e verificação da **Task**. 
### Problemas que isso pode causar:

- `Falta de nexo` - uma classe não deve assumir responsabilidades que não são suas;
- `Muita informação junta`  - sua classe vai ficar com muitas dependências e uma grande dificuldade para alterações; 
- `Dificuldades na implementação de testes automatizados` - é difícil de [“mockar”](https://pt.wikipedia.org/wiki/Objeto_Mock) esse tipo de classe;

Agora aplicando **SRP** na classe *Task*, vamos ver a melhora que esse principio pode causar: 

```typescript
class TaskHandler{ 
	createTask() {/*...*/} 
	updateTask() {/*...*/} 
	deleteTask() {/*...*/} 
} 

class TaskViewer{ 
	showAllTasks() {/*...*/} 
} 

class TaskChecker { 
	existsTask() {/*...*/} 
} 

class TaskCompleter { 
	completeTask() {/*...*/} 
}
```
>Daria para colocar create, update e delete em classes separadas, mas ao depender do contexto e tamanho do projeto é bom evitar complexidade desnecessária.

Talvez você tenha se perguntado `só vou conseguir aplicar isso em classes?` não, pelo contrário, da para aplicar em métodos e funções também.

```typescript
//❌
function emailClients(clients: IClient[]) {

	clients.forEach((client)=>{
		const clientRecord = db.find(client);
		
		if(clientRecord){
			sendEmail(client);
		}
	})
}

//✅
function isClientActive(client: IClient):boolean { 
	const clientRecord = db.find(client); 
	return !!clientRecord; 
}

function getActiveClients(clients: IClient[]):<IClient | undefined> { 
	return clients.filter(isClientActive); 
}

function emailClients(clients: IClient[]):void { 
	const activeClients = getActiveClients(clients);
	activeClients?.forEach(sandEmail); 
}
```

Código mais bonito, elegante e organizado. Esse principio é a base para os outros, ao conseguir aplicá-lo você estará fazendo um código de ótima qualidade, de fácil leitura e de fácil manutenção. 

## O - Princípio Aberto Fechado

![exemplo Princípio Aberto-Fechado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qk0e24cnjefd5e8r0h6c.png)

>*OCP - Open-Closed Principle*

Esse principio diz que **Objetos ou entidades devem estar abertos para extensão, mas fechados para modificação**, se for necessário adicionar uma funcionalidade é melhor estender e não alterar seu código fonte. 

Imagine um pequeno sistema para secretaria de escolar, nele existem duas classes que representa a grade de aulas dos alunos, ensino fundamental e ensino médio. Além de uma classe que é para definir as aulas do aluno. 

```typescript
class EnsinoFundamental {
	gradeCurricularFundamental(){}
}

class EnsinoMedio {
    gradeCurricularMedio(){}
}

class SecretariaEscola {
	aulasDoAluno: string; 
	
	cadastrarAula(aulasAluno){
		if(aulasAluno instanceof EnsinoFundamental){
			this.aulasDoAluno = aulasAluno.gradeCurricularFundamental();
		} else if(aulasAluno.ensino instanceof EnsinoMedio){
			this.aulasDoAluno = aulasAluno.gradeCurricularMedio();
		}
	}
}
```

A classe `SecretariaEscola` é responsável por verificar qual o ensino do aluno para conseguir aplicar a regra de negócio certa na hora do cadastrar as aulas. Agora imagine que essa escola tenha adicionado o ensino técnico e sua grade de aulas no sistema, vai ser necessário modificar essa classe, certo?, só que ai você esbarra em um problema, o de violar o `Princípio Aberto-Fechado` do *SOLID*. 

Qual a solução que te vem a cabeça? Provavelmente adicionar um `else if` na classe e pronto, problema resolvido 😁. Não pequeno Padawan 😐, ai que está o problema! 

**Alterar uma classe já existente para adicionar um novo comportamento, corremos um sério risco de introduzir bugs em algo que já estava funcionando.**

>**Lembre-se:** **OCP** preza que uma classe deve estar fechada para alteração e aberta para extensão.

Veja a beleza que fica ao refatorar o código: 

```typescript
interface gradeCurricular {
    gradeDeAulas();
}

class EnsinoFundamental implements gradeCurricular {
    gradeDeAulas(){}
}
  
class EnsinoMedio implements gradeCurricular {
    gradeDeAulas(){}
}

class EnsinoTecnico implements gradeCurricular {
    gradeDeAulas(){}
}

class SecretariaEscola {
    aulasDoAluno: string;

    cadastrarAula(aulasAluno: gradeCurricular) {
        this.aulasDoAluno = aulasAluno.gradeDeAulas();
    }
}
```

Veja a classe `SecretariaEscola` ela não precisa mais saber quais os métodos chamar para cadastrar a aula. Ela será capaz de cadastrar a grade de aulas corretamente de qualquer novo tipo de modalidade de ensino que seja criado, observe que adicionei o `EnsinoTecnico` sem nenhuma necessidade de mudar o código-fonte. 

>*Desde que implemente a interface `gradeCurricular`.*

>Separe o comportamento extensível por trás de uma interface e inverta as dependências.  
>Uncle Bob

- `Aberto para extensão`: você pode adicionar alguma nova funcionalidade ou comportamento a classe sem alterar seu código-fonte. 
- `Fechado para modificação`: se sua classe já tem uma funcionalidade ou comportamento que não apresenta problema algum, não altere o código-fonte dela para colocar algo novo.

## L - Princípio da substituição de Liskov

![exemplo Princípio da substituição de Liskov](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eyrl3p96aqzowf72ipcb.png)

>*LSP - Liskov Substitution Principle*

Princípio da substituição de Liskov — **Uma** **classe derivada deve ser substituível por sua classe base**.

Esse principio que o *mano* Liskov introduziu em uma conferência no ano de 1987 é um pouco complicada de se entender ao ler a explicação dele, mas não se preocupe, vou mostrar outra explicação e um exemplo que vai te ajudar a entender.

> Se para cada objeto o1 do tipo S há um objeto o2 do tipo T de forma que, para todos os programas P definidos em termos de T, o comportamento de P é inalterado quando o1 é substituído por o2 então S é um subtipo de T

Entendeu? Não né, eu também não entendi na primeira vez que li isso (nem nas outras dez vezes), mas calma aí, existe outra explicação:

> Se S é um subtipo de T, então os objetos do tipo T, em um programa, podem ser substituídos pelos objetos de tipo S sem que seja necessário alterar as propriedades deste programa. — [Wikipedia](https://pt.wikipedia.org/wiki/Princ%C3%ADpio_da_substitui%C3%A7%C3%A3o_de_Liskov).

Se você é mais visual calma que tenho exemplos em código: 

```typescript
class Fulano {
    falarNome() {
        return "sou fulano!";
    }
}

class Sicrano extends Fulano {
    falarNome() {
        return "sou sicrano!";
    }
}

const a = new Fulano();
const b = new Sicrano();

function imprimirNome(msg: string) {
    console.log(msg);
}

imprimirNome(a.falarNome()); // sou fulano!
imprimirNome(b.falarNome()); // sou sicrano!

```

A classe pai e a classe derivada estão passando como parâmetro e o código continua funcionando da forma esperada, magica? Que nada, é o princípio do nosso mano Liskov. 

### Exemplos de violações:

- Sobrescrever/implementar um método que não faz nada;
- Lançar uma exceção inesperada;
- Retornar valores de tipos diferentes da classe base;

## I - Princípio da Segregação da Interface

![exemplo Princípio da Segregação da Interface
](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p88do8ivd00s9aofo5yq.png)

>*ISP - Interface Segregation Principle*

Princípio da Segregação da Interface — **Uma classe não deve ser forçada a implementar interfaces e métodos que não irão utilizar.**

Esse principio diz que é melhor criar interfaces mais especificas do que uma interface genérica.

No exemplo a seguir foi criado uma interface `Animal` para abstrair os comportamentos de animais e em seguida as classes implementam essa interface, veja:

```typescript
interface Animal {
	comer();
	dormir();
	voar();
}

class Pato implements Animal{
	comer(){/*faz algo*/};
	dormir(){/*faz algo*/};
	voar(){/*faz algo*/};
}

class Peixe implements Animal{
	comer(){/*faz algo*/};
	dormir(){/*faz algo*/};
	
	voar(){/*faz algo*/};
	// Esta implementação não faz sentido para um peixe 
	// ela viola o Princípio da Segregação da Interface
}
```

A interface genérica `Animal` esta forçando a classe `Peixe` a ter um comportamento que faz sentido e acaba viola o principio **ISP** e o **LSP** também. 

Resolvendo esse problema usando o **ISP**:

```typescript
interface Animal {
	comer();
	dormir();
}

interface AnimalQueVoa extends Animal {
	voar();
}

class Peixe implements Animal{
	comer(){/*faz algo*/};
	dormir(){/*faz algo*/};
}

class Pato implements AnimalQueVoa {
	comer(){/*faz algo*/};
	dormir(){/*faz algo*/};
	voar(){/*faz algo*/};
}
```

Agora ficou melhor, foi retirado o método `voar()` da interface `Animal` e adicionamos em uma interface derivada `AnimalQueVoa`. Com isso o comportamento foi isolado de maneira correta dentro do nosso contexto e ainda respeitamos o principio de segregação das interfaces. 

## D - Princípio da inversão da dependência

![exemplo Princípio da inversão da dependência](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9s5ywa9xijb41c1z2l1a.png)

> *DIP — Dependency Inversion Principle*

Princípio da Inversão de Dependência — **Dependa de abstrações e não de implementações.**

> 1. Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender da abstração.
> 
> 2. Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações. 
> 
>  - *Uncle Bob*

No exemplo a seguir vou mostrar um codigo simples para ilustrar o **DIP**. Nesse exemplo temos um sistema de notificação que envia mensagens por diferentes meios, como e-mail e SMS. Primeiro vamos criar classes concretas para esses meios de notificação: 

```typescript
class EmailNotification {
  send(message) {
    console.log(`Enviando e-mail: ${message}`);
  }
}

class SMSNotification {
  send(message) {
    console.log(`Enviando SMS: ${message}`);
  }
}
```

Agora, vamos criar uma classe de serviço que depende dessas implementações concretas:

```typescript
class NotificationService {
  constructor() {
    this.emailNotification = new EmailNotification();
    this.smsNotification = new SMSNotification();
  }

  sendNotifications(message) {
    this.emailNotification.send(message);
    this.smsNotification.send(message);
  }
}
```

No exemplo acima, `NotificationService` depende diretamente das implementações concretas de `EmailNotification` e `SMSNotification`. Isso viola o DIP, pois a classe de alto nível `NotificationService` está diretamente dependente de classes de baixo nível.

Vamos corrigir esse código usando **DIP**. Em vez de depender de implementações concretas, a classe de alto nível `NotificationService` deve depender de abstrações. Vamos criar uma interface `Notification` como abstração: 

```typescript
// Abstração para o envio de notificações
interface Notification { 
	send(message: string): void
}
```

Agora, as implementações concretas `EmailNotification` e `SMSNotification` devem implementar essa interface:

```typescript
class EmailNotification implements Notification {
  send(message: string) {
    console.log(`Enviando e-mail: ${message}`);
  }
}

class SMSNotification implements Notification {
  send(message: string) {
    console.log(`Enviando SMS: ${message}`);
  }
}
```

Finalmente, a classe de serviço de notificação pode depender da abstração `Notification`:

```typescript
class NotificationService {

private notificationMethod: Notification;

  constructor(notificationMethod: Notification) {
    this.notificationMethod = notificationMethod;
  }

  sendNotification(message: string) {
    this.notificationMethod.send(message);
  }
}
```

Dessa forma, a classe de serviço `NotificationService` depende de uma abstração `Notification`, e não das implementações concretas, cumprindo assim o **Princípio da Inversão de Dependência**.

## Conclusão 

Ao adotar esses princípios, os desenvolvedores podem criar sistemas mais resilientes às mudanças, facilitando a manutenção e melhorando a qualidade do código ao longo do tempo. 

Todo esse conteúdo foi baseado em anotações, outros artigos e vídeos que encontrei pela internet durante meu estudo sobre POO, as explicações são próximas aos autores dos princípios, já os códigos usados nos exemplos eu criei baseado no meu entendimento dos princípios. Espero ter ajudado a você leitor na progressão dos seus estudos.