Skip to content

Latest commit

 

History

History
370 lines (293 loc) · 11.4 KB

solid.md

File metadata and controls

370 lines (293 loc) · 11.4 KB
src theme class highlighter mdc drawings image selectable colorSchema title author export
./cover.md
default
text-center
shiki
true
persist
/side-logo.png
true
dark
S.O.L.I.D.
Pablo Leon Rodrigues
format timeout withClicks
pdf
30000
false


layout: image-right image: /RobertCMartin.jpg


Contexto

Publicado em 2000 em um paper intitulado "Design Principles and Design Patterns" por Robert C. Martin (Aka. Uncle Bob).

SOLID é um acrônimo para cinco princípios de design orientado a objetos que visam melhorar a legibilidade, manutenção e flexibilidade do código.


S - Single Responsibility Principle (SRP) {style="color: blue;"}

O SRP define que uma classe ou módulo deve ter apenas uma razão para mudar, ou seja, deve possuir uma única responsabilidade. Em termos simples, cada classe, função, ou módulo deve focar em realizar uma única tarefa ou propósito.

Se uma classe tem várias responsabilidades, ela pode ser afetada por mudanças em qualquer uma dessas responsabilidades, tornando o código mais suscetível a falhas.

  • Manutenção facilitada:{style="color: green;"} Se uma classe faz apenas uma coisa, é mais fácil localizar e corrigir problemas quando algo der errado.
  • Redução de acoplamento:{style="color: green;"} Classes com responsabilidades únicas tendem a ser menos dependentes umas das outras, o que facilita a refatoração e evolução do sistema.
  • Reutilização de código:{style="color: green;"} Classes pequenas e especializadas são mais fáceis de reutilizar em diferentes contextos.
  • Testabilidade:{style="color: green;"} Classes que cumprem apenas uma responsabilidade têm menos dependências e cenários a serem testados, facilitando a criação de testes unitários.

layout: two-cols

class Report {
  generate() {
    // gerar o relatório
  }

  format() {
    //   formatar o relatório
  }

  sendByEmail() {
    // lógica para enviar
  }
}

::right::

class ReportGenerator {
  generate() {
    //gerar o relatório
  }
}

class ReportFormatter {
  format() {
    //formatar o relatório
  }
}

class EmailSender {
  send(report: Report) {
    // lógica para enviar
  }
}

O - Open/Closed Principle (OCP){style="color: blue;"}

O Open/Closed Principle afirma que uma classe, módulo ou função deve estar aberto para extensão, mas fechado para modificação. Isso significa que o comportamento de um sistema deve poder ser estendido sem a necessidade de alterar o código-fonte existente.

A ideia principal é separar o código que pode mudar do que permanece estável. Para isso, você pode usar abstrações, como interfaces, herança e polimorfismo, dependendo da linguagem utilizada.

  • Maior flexibilidade e extensibilidade:{style="color: green;"} Você pode adicionar novos comportamentos ao software sem alterar seu funcionamento atual, facilitando a evolução do código.
  • Menos risco de bugs:{style="color: green;"} Como você não modifica o código que já está em produção, reduz-se o risco de introduzir novos problemas ao adicionar funcionalidades.
  • Reutilização de código:{style="color: green;"} As abstrações utilizadas para aplicar o OCP incentivam a reutilização de componentes, o que torna o código mais eficiente.

layout: two-cols

class Discount {
  calculate(type: string): number {
    if (type === 'regular') {
      return 0.1;
    } else if (type === 'vip') {
      return 0.2;
    } else {
      return 0;
    }
  }
}

::right::

interface Discount {
  calculate(): number;
}

class Regular implements Discount {
  calculate(): number {
    return 0.1;
  }
}
class VIP implements Discount {
  calculate(): number {
    return 0.2;
  }
}

layout: image-right image: /BarbaraLiskov.jpg backgroundSize: fit

L - Liskov Substitution Principle (LSP){style="color: blue;"}

O princípio de substituição de Liskov, proposto por Barbara Liskov, afirma que objetos de uma classe derivada (subclasse) devem poder ser substituídos por objetos da classe base (superclasse) sem alterar as propriedades corretas do programa.

Se você tem uma classe base e uma subclasse que herda dela, a subclasse deve preservar o comportamento esperado da classe base. Caso contrário, a herança não está sendo aplicada corretamente, e isso pode quebrar o código que utiliza a classe base.


layout: two-cols

class Bird {
  fly() {
    console.log("pássaro voando");
  }
}

class Penguin extends Bird {
  fly() {
    throw new Error("Pinguins não voam");
  }
}

::right::

class Bird {
  // comportamentos de todos os pássaros
}

class FlyingBird extends Bird {
  fly() {
    console.log("pássaro voando");
  }
}

class Penguin extends Bird {
  // pinguins não podem voar
  swim() {
    console.log("O pinguim nada");
  }
}

I - Interface Segregation Principle (ISP){style="color: blue;"}

O ISP afirma que os clientes não devem ser forçados a depender de interfaces que não utilizam. Em outras palavras, uma interface deve ser específica para as necessidades dos clientes que a utilizam, em vez de ser uma interface "inchada" com métodos que alguns clientes não precisam.

Isso significa que é melhor ter várias interfaces pequenas e especializadas, cada uma com métodos diretamente relacionados a um determinado comportamento, do que uma única interface grande e generalizada.

  • Evita a implementação de métodos desnecessários:{style="color: green;"} As classes só implementam o que realmente precisam, reduzindo a complexidade.
  • Menor acoplamento:{style="color: green;"} Cada cliente só depende de uma interface que faz sentido para ele, o que torna o código mais modular.
  • Facilidade de manutenção:{style="color: green;"} Interfaces menores são mais fáceis de entender, modificar e manter, já que mudanças em uma parte do sistema não impactam outras desnecessariamente.

layout: two-cols

interface MultiPrinter {
  print(): void;
  scan(): void;
  fax(): void;
}
class Printer implements MultiPrinter {
  print(): void {
    console.log("Imprimindo...");
  }
  scan(): void {
  }
  fax(): void {
  }
}

::right::

Podemos refatorar o código e criar multiplas interfaces

interface Printer {
  print(): void;
}

interface Scanner {
  scan(): void;
}

interface Fax {
  fax(): void;
}

layout: two-cols

class SimplePrinter implements Printer {
  print(): void {
    console.log("Imprimindo...");
  }
}

class OfficePrinter 
  implements Printer, Fax {
    print(): void {
      console.log("Imprimindo...");
    }

    fax(): void {
      console.log("Enviando fax...");
    }
}

::right::

class AdvancedPrinter 
  implements Printer, Scanner, Fax {
    print(): void {
      console.log("Imprimindo...");
    }

    scan(): void {
      console.log("Escaneando...");
    }

    fax(): void {
      console.log("Enviando fax...");
    }
}

D - Dependency Inversion Principle (DIP){style="color: blue;"}

O DIP afirma que módulos de alto nível não devem depender de módulos de baixo nível, ele sugere que devemos depender de interfaces ou abstrações em vez de depender de implementações concretas. Isso reduz o acoplamento entre as diferentes partes de um sistema, tornando-o mais flexível e fácil de modificar.

  • Desacoplamento:{style="color: green;"} O DIP reduz o acoplamento entre diferentes partes do sistema, permitindo que mudanças em uma parte não exijam mudanças em outra.
  • Facilidade para testar:{style="color: green;"} Quando dependemos de abstrações, é mais fácil substituir implementações reais por mocks ou stubs em testes.
  • Flexibilidade:{style="color: green;"} O sistema pode evoluir e adaptar-se a novos requisitos sem grandes refatorações, uma vez que as implementações podem ser alteradas sem impactar os módulos de alto nível.
  • Reutilização de código:{style="color: green;"} Interfaces e abstrações bem definidas promovem a reutilização de módulos, já que eles podem trabalhar com diferentes implementações.

Nesse caso, a classe NotificationManager (alto nível) depende diretamente da classe EmailService (baixo nível). Isso é uma violação do DIP, porque NotificationManager está fortemente acoplado a um serviço específico para enviar notificações. Se um dia for necessário mudar para um serviço de notificação por SMS, ou adicionar outros tipos de notificação, será necessário modificar o código de NotificationManager, o que quebra a flexibilidade e a modularidade.

class EmailService {
  sendEmail(to: string, message: string): void {
    console.log(`Enviando e-mail para ${to}: ${message}`);
  }
}
class NotificationManager {
  private emailService: EmailService;
  constructor() {
    this.emailService = new EmailService();
  }
  notify(to: string, message: string): void {
    this.emailService.sendEmail(to, message);
  }
}

Para corrigir, podemos criar uma abstração (interface).

interface NotificationService {
  send(to: string, message: string): void;
}
class EmailService implements NotificationService {
  send(to: string, message: string): void { ... }
}
class SMSService implements NotificationService {
  send(to: string, message: string): void { ... }
}
class NotificationManager {
  private notificationService: NotificationService;
  constructor(notificationService: NotificationService) {
    this.notificationService = notificationService;
  }
  notify(to: string, message: string): void {
    this.notificationService.send(to, message);
  }
}