DEV Community

Determinado 96
Determinado 96

Posted on

Um resumo sobre coesão e SRP

O que é uma classe coesa?

Uma classe coesa é aquela que tem apenas uma responsabilidade.
Classes coesas são menores, mais organizadas, fáceis de manter e reutilizar.

Exemplo de classe com baixa coesão

class CalculadoraDeSalario {
  calcula(funcionario) {
    if (funcionario.cargo === "DESENVOLVEDOR") {
      return this.dezOuVintePorcento(funcionario);
    }

    if (funcionario.cargo === "DBA" || funcionario.cargo === "TESTER") {
      return this.quinzeOuVinteCincoPorcento(funcionario);
    }

    throw new Error("Funcionário inválido");
  }

  dezOuVintePorcento(funcionario) {
    if (funcionario.salarioBase > 3000) {
      return funcionario.salarioBase * 0.8;
    }
    return funcionario.salarioBase * 0.9;
  }

  quinzeOuVinteCincoPorcento(funcionario) {
    if (funcionario.salarioBase > 2000) {
      return funcionario.salarioBase * 0.75;
    }
    return funcionario.salarioBase * 0.85;
  }
}
Enter fullscreen mode Exit fullscreen mode

Problemas dessa classe

  • A classe precisa saber todos os cargos.
  • Muitos ifs → sempre que surgir um novo cargo, a classe precisa ser alterada.
  • As regras de cálculo não podem ser reutilizadas facilmente.
  • A classe tem mais de uma responsabilidade (decidir o cargo + calcular o salário).

Ou seja, baixa coesão e alto acoplamento.

O ideal seria cada regra de cálculo estar em uma classe separada. Isso aumenta a coesão e facilita reutilização e manutenção.

Por que essa classe é difícil de reutilizar?

Imagine que você queira reutilizar apenas a regra dezOuVintePorcento() em outro lugar do sistema.

Você não consegue usar só o método, porque ele está dentro da classe CalculadoraDeSalario.

Então você é obrigado a:

  • Instanciar a classe
  • Criar um objeto funcionário
  • Passar o funcionário
  • Só então usar o método

Ou seja, para reutilizar uma regra de cálculo, você precisa levar a classe inteira junto.

Isso é um sinal claro de baixa coesão, porque a classe está fazendo coisas demais.

Exemplo de classe refatorada com alta coesão

class Cargo {
  calculaSalario(salarioBase) {
    throw new Error("Método deve ser implementado pela subclasse");
  }
}

class Desenvolvedor extends Cargo {
  calculaSalario(salarioBase) {
    return salarioBase > 3000
      ? salarioBase * 0.8
      : salarioBase * 0.9;
  }
}

class Dba extends Cargo {
  calculaSalario(salarioBase) {
    return salarioBase > 2000
      ? salarioBase * 0.75
      : salarioBase * 0.85;
  }
}

class Tester extends Cargo {
  calculaSalario(salarioBase) {
    return salarioBase > 2000
      ? salarioBase * 0.75
      : salarioBase * 0.85;
  }
}

const RegrasPorCargo = {
  DESENVOLVEDOR: new Desenvolvedor(),
  DBA: new Dba(),
  TESTER: new Tester()
};

class CalculadoraDeSalarioRefatorada {
  calcula(funcionario) {
    const regra = RegrasPorCargo[funcionario.cargo];

    if (!regra) {
      throw new Error("Funcionário inválido");
    }

    return regra.calculaSalario(funcionario.salarioBase);
  }
}

const funcionario2 = {
  cargo: "DESENVOLVEDOR",
  salarioBase: 3500
};

const calculadora2 = new CalculadoraDeSalarioRefatorada();

console.log(calculadora2.calcula(funcionario2));
Enter fullscreen mode Exit fullscreen mode

Primeiro passo para entender a refatoração

Antes da refatoração, a classe CalculadoraDeSalario mudava por dois motivos:

  • Quando surgia um novo cargo
  • Quando surgia uma nova regra de cálculo

Ou seja, a classe tinha mais de um motivo para mudar, o que quebra o SRP (Single Responsibility Principle).

Se observarmos os métodos de cálculo, mesmo sendo diferentes, todos têm o mesmo formato:

  • Recebem o salário
  • Aplicam uma regra
  • Retornam o salário calculado

Ou seja, todos seguem a mesma abstração.
Então a ideia da refatoração é criar uma classe para cada regra de cálculo, todas com o mesmo método calculaSalario().

  • O que ganhamos com isso?
  • Cada regra fica isolada
  • Uma mudança em uma regra não afeta as outras
  • As classes ficam menores
  • As classes ficam mais coesas
  • O sistema fica mais fácil de manter
  • Podemos adicionar novos cargos sem alterar código existente

Agora cada classe tem apenas um motivo para mudar: a sua própria regra de cálculo.

Por que não separar em pequenos métodos?

Se separamos um método grande em pequenos métodos não teremos reuso isolado.

Referência

Este exemplo e a ideia de refatoração foram inspirados no livro: Orientação a Objetos e SOLID para Ninjas - Projetando classes flexíveis - MAURÍCIO ANICHE

Top comments (0)