Skip to content

Instantly share code, notes, and snippets.

@reginadiana
Last active September 24, 2024 11:57
Show Gist options
  • Save reginadiana/ab75c9faca26623ef7a708e1c07369dd to your computer and use it in GitHub Desktop.
Save reginadiana/ab75c9faca26623ef7a708e1c07369dd to your computer and use it in GitHub Desktop.
Anotações sobre SOLID

SRP - Single Responsability Principle

Também conhecido como Principio da Responsabilidade Unica, esse principio diz que uma classe deve ter apenas um motivo para mudar.

Um exemplo de sintoma de que este principio está sendo violado é ter uma classe que lida com multiplos atores.

No exemplo abaixo temos uma classe Order com vários métodos que lidam com o CRUD de pedidos, visualização de dados e interface com o banco de dados ou API, ou seja, uma mesma classe tem as responsabilidades de modelar os dados, salvar, etc.

class Order {
    public function calculateTotalSum(){/*...*/}
    public function getItems(){/*...*/}
    public function getItemCount(){/*...*/}
    public function addItem(item){/*...*/}
    public function deleteItem(item){/*...*/}

    public function printOrder(){/*...*/}
    public function showOrder(){/*...*/}

    public function load(){/*...*/}
    public function save(){/*...*/}
    public function update(){/*...*/}
    public function delete(){/*...*/}
}

Uma solução seria dividir esses métodos em 3 classes distintas, que lidam exclusivamente com a interface com banco de dados ou API (Repository), com a visualização de dados (Viewer) e CRUD (Order)

class Order {
    public function calculateTotalSum(){/*...*/}
    public function getItems(){/*...*/}
    public function getItemCount(){/*...*/}
    public function addItem(item){/*...*/}
    public function deleteItem(item){/*...*/}
}

class OrderRepository {
    public function load(orderID){/*...*/}
    public function save(order){/*...*/}
    public function update(order){/*...*/}
    public function delete(order){/*...*/}
}

class OrderViewer {
    public function printOrder(order){/*...*/}
    public function showOrder(order){/*...*/}
}

OCP - Open Closed Principle

Também conhecido como Principio do Aberto Fechado, esse principio diz que uma classe deve estar aberta para extensão e fechada para alteração.

Vamos supor que em uma empresa temos 2 tipos de funcionados: os estáriários e CLT's. Na codebase temos 3 classes, a do estágiário (que recebe bolsa auxilio), a do CLT (que receba um salário) e a folha de pagamento. A classe da folha de pagamento é chamada para calcular o valor da folha, porém a forma de calcular o valor varia conforme o ator (se é estagiário ou CLT). Nessa classe, usamamos ifs para distinguir esses atores e chamamos os métodos conforme for a origem da remuneração (bolsa auxilio ou salário).

class Trainee {
  public function assistence() {/* */}
}

class Employee {
  // Salário
  public function wage() {/* */}
}

// Folha de pagamento
class Payroll() {
  public function total(user) {
    if (user.type === 'trainee') {
      return assistence()
    } else if (user.type === 'employee') {
      return wage()
    }
  }
}

Supondo que a empresa aceite um novo tipo de colaborador, o PJ, a tendencia seria continuar implementando o comportamento que já existe criando uma nova classe para PJ e um novo if método total. Isso é um problema pois temos a classe da folha de pagamento não deveria conhecer a forma com que a remuneração é calculada para cado tipo de colaborador.

Uma solução seria criar uma interface Remuneravel para impor que toda classe de colaborardor (PJ, CLT e estagiário) tenham um método para calculo de remuneração. Dessa forma, a classe de folha de pagamento pode chamar apenas um método sempre e não se prepcupar com a origem de remuneração em cada caso. Essa solução não só faz com que não modifiquemos a folha de pagamento no futuro ao termos novos tipos de colaboradores como aplica o principio de SRP ao isolar as responsabilidades.

LSP - Liskov Substitution Principle

Também conhecido como Principio de Subistituição de Liskov, esse principio diz que uma classe deve ter os mesmos métodos e estes devem ter o comportamento esperado de sua classe pai que foi herdada. Exemplo, supondo que temos uma classe Animal que possui os métodos Breath e Eat. Agora imagine que temos mais 2 classes Dog e Cat que herdam de Animal. De acordo com o principio, essas 2 classes devem se manter consistentes em relação aos métodos Breath e Eat.

interface Animal {
  public function breath();
  public function eat();
}

class Dog extends Animal {
  public function breath() {
    // Cachorro respira rápido, mas respira
  }
  
  public function eat() {
    // Cachorro come rápido, mas come
  }
}

class Cat extends Animal {
  public function breath() {
    // Gato respira devagar, mas respira
  }
  
  public function eat() {
    // Gato come devagar, mas come
  }
}

Alguns desses comportamentos violam essa regra, como:

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

ISP - Interface Segregation Principal

Também conhecido como Principio da Segregação de Internface, esse principio diz que devemos usar interfaces especificas para não violar a LSP. Exemplo, supondo que temos uma classe Bird que possui os métodos Fly e Walk. Agora imagine que temos 2 classes, Duck e Hen que herdam de Bird. Temos um problema nesse cenário pois a classe Hen não tem o comportamento de Fly (galinhas não voam).

interface Bird {
  public function fly();
  public function walk();
}

class Duck implements Bird {
  public function fly() {
    // Pato voa
  }
  
  public function walk() {
    // Pato anda
  }
}

class Hen implements Bird {
  public function fly() {
    // X -> Gatinha nã voa
  }
  
  public function walk() {
    // Galinha anda
  }
}

A soluçao para este cenário seria ter uma interface especifica para aves que voam, e esta pode herdar da classe Bird, que por sua vez não terá o método Fly, pois é algo espefico.

interface Bird {
  public function walk();
}

interface FlyingBird extends Bird {
  public function fly();
}

class Duck implements FlyingBird {
  public function fly() {
    // Pato voa
  }
  
  public function walk() {
    // Pato anda
  }
}

class Hen implements Bird {  
  public function walk() {
    // Galinha anda
  }
}

DIP - Dependency Inversion Principle

Também conhecido como Principio da Inversão de Dependencia, esse principio diz que módulos de alto nível não devem dependender diretamente de módulos de baixo nível mas que ambos devem se comunicar via interface.

Importante: Inversão de Dependência não é igual a Injeção de Dependência, fique ciente disso! A Inversão de Dependência é um princípio (Conceito) e a Injeção de Dependência é um padrão de projeto (Design Pattern).

Módulo de alto nível também são conhecidos como componentes abstratos e lidam com regras de negocio Módulo de baixo nível também são conhecidos como componentes concretos e lidam com a implementacao/tecnologia especifica.

Exemplo, supondo que temos uma classe para cadastro, o SignUp, que possiu o método register e este faz uma instancia do banco de dados em SQL para criar uma nova conta. Temos um problema aqui pois o módulo de alto nível que é o SignUp está dependendo de um módulo de baixo nível, que é o SQL, para funcionar.

import sql from 'sql';

class SignUp() {
  public function register() {
     // Depende diretamente da implementação do SQL
     const connection = sql.connect(); 
     
    // Cadastra nova conta
  }
}

A solução que este principio trás é de ter uma interface para Database que possui seu método de create, termos uma classe de SQL que herda do Database e ao usar o SignUp, este deve receber a conexão com o banco como parametro.

interface Database {
  public function create(params);
}
class SQLDatabase implements Database {
  public function create(params) {     
    // Salva registro no banco
  }
}
class SignUp {
  public function register(connection, params) {     
    // Cadastra nova conta
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment