Este artigo é a parte 2 de 5 na série SOLID

O Princípio do Aberto/Fechado (Open-Closed Principle, ou OCP) é o segundo dos cinco princípios SOLID e é essencial para criar códigos que sejam fáceis de estender e manter. O princípio afirma que:

Entidades de software (classes, métodos, módulos, etc.) devem estar abertas para extensão, mas fechadas para modificação.”

Em outras palavras, o código deve ser projetado de tal forma que possa ser facilmente estendido para novas funcionalidades sem a necessidade de alterar o código existente.

O Que Significa Estar Aberto para Extensão e Fechado para Modificação?

  • Aberto para Extensão: Significa que o comportamento de uma classe ou módulo deve ser facilmente ampliado. Se precisar adicionar uma nova funcionalidade, isso deve ser possível sem mexer no código que já existe.
  • Fechado para Modificação: Significa que o código existente não deve ser alterado para evitar introduzir novos erros e preservar funcionalidades que já estão testadas e funcionando.

Seguindo esse princípio, é possível criar sistemas mais robustos e fáceis de manter, pois evita-se alterar partes que já estão funcionando corretamente, ao mesmo tempo em que torna o código mais flexível e preparado para mudanças.

Problemas de Não Seguir o OCP

  1. Alta Propensão a Erros: Cada vez que alteramos código existente, corremos o risco de introduzir novos bugs, mesmo em áreas que antes estavam funcionando corretamente.
  2. Dificuldade de Manutenção: Se todas as novas funcionalidades exigirem modificações nas classes existentes, a manutenção do sistema se torna mais complexa e arriscada.
  3. Falta de Escalabilidade: Sistemas que precisam ser frequentemente modificados para adicionar novas funcionalidades são mais difíceis de escalar, pois o risco de introduzir erros aumenta exponencialmente.

Exemplo Prático em Dart

Vamos imaginar uma situação em que temos uma classe que calcula descontos com base em diferentes tipos de clientes. Uma implementação que não segue o OCP poderia ser algo assim:

Classe que Viola o OCP:

class DiscountService {
  double calculateDiscount(String customerType, double amount) {
    if (customerType == 'Regular') {
      return amount * 0.1;
    } else if (customerType == 'Premium') {
      return amount * 0.2;
    } else {
      return 0.0;
    }
  }
}

Nessa implementação, se quisermos adicionar um novo tipo de cliente, teríamos que modificar a classe DiscountService, adicionando mais um if. Esse tipo de abordagem quebra o princípio do aberto/fechado, pois estamos alterando a classe existente sempre que precisamos adicionar novos comportamentos.

Classe Refatorada Seguindo o OCP:

Podemos melhorar o design aplicando o princípio de aberto/fechado, utilizando polimorfismo. Vamos criar uma abstração para os diferentes tipos de descontos:

abstract class DiscountStrategy {
  double calculate(double amount);
}

class RegularDiscount implements DiscountStrategy {
  @override
  double calculate(double amount) {
    return amount * 0.1;
  }
}

class PremiumDiscount implements DiscountStrategy {
  @override
  double calculate(double amount) {
    return amount * 0.2;
  }
}

class NoDiscount implements DiscountStrategy {
  @override
  double calculate(double amount) {
    return 0.0;
  }
}

class DiscountService {
  final DiscountStrategy discountStrategy;

  DiscountService(this.discountStrategy);

  double getDiscount(double amount) {
    return discountStrategy.calculate(amount);
  }
}

void main() {
  DiscountService regularDiscountService = DiscountService(RegularDiscount());
  print('Desconto Regular: ${regularDiscountService.getDiscount(100)}');

  DiscountService premiumDiscountService = DiscountService(PremiumDiscount());
  print('Desconto Premium: ${premiumDiscountService.getDiscount(100)}');

  DiscountService noDiscountService = DiscountService(NoDiscount());
  print('Sem Desconto: ${noDiscountService.getDiscount(100)}');
}

Agora, temos um sistema que segue o princípio do aberto/fechado:

  • DiscountStrategy é uma abstração que define um contrato para cálculo de desconto.
  • Cada tipo de desconto (às classes RegularDiscount, PremiumDiscount, NoDiscount) implementa a abstração, permitindo adicionar novos tipos de descontos sem alterar a lógica existente na classe DiscountService.

Dessa forma, se precisarmos adicionar um novo tipo de desconto, como um VIPDiscount, basta criar uma nova classe que implemente DiscountStrategy, sem alterar o código existente. Isso garante que o sistema está aberto para extensão, mas fechado para modificação.

Benefícios do Princípio do Aberto/Fechado

  1. Redução de Erros: Ao adicionar novas funcionalidades sem modificar o código existente, evitamos introduzir novos bugs em partes do sistema que já estão funcionando corretamente.
  2. Facilidade de Manutenção: Estender o comportamento do sistema se torna mais simples, pois não precisamos alterar código já existente e, consequentemente, reduzimos o risco de impacto em outras áreas.
  3. Reutilização e Escalabilidade: Código bem projetado que segue o OCP tende a ser mais reutilizável e escalável, pois novas funcionalidades podem ser incorporadas sem impactar o que já existe.

Resumo

O Princípio do Aberto/Fechado é essencial para garantir que sistemas sejam fáceis de evoluir, sem a necessidade de modificar aquilo que já está funcionando. Ao projetar sistemas que estejam abertos para extensão e fechados para modificação, é possível reduzir a quantidade de bugs introduzidos em manutenções e facilitar a incorporação de novas funcionalidades.

Seguir o OCP resulta em um código mais modular, de fácil manutenção e extensível, o que contribui para um desenvolvimento mais ágil e robusto a longo prazo. A aplicação desse princípio ajuda a minimizar riscos durante a evolução de software e promove um design que se adapta bem às mudanças inevitáveis dos requisitos de negócio.