DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on

OCP — Open/Closed Principle (Princípio Aberto/Fechado)

Princípio Aberto/Fechado

Esse é o segunda pilar do SOLID. Sua fundamentação existe para que os softwares criados sejam fáceis de manter e evoluir sem quebrar o que já funciona.

Certo! Mas o que diabos isso significa? Os sistemas não são estáticos, eles tendem a evoluír. O cenário comum no mercado, portanto, é: com novas definições de funcionalidades os desenvolvedores geralmente alteram o código-fonte existente, código este, que já foi testado e homologado. Resultando em um sistema difícil de manter e “sustentar”.

O OCP surge para mitigar esse problema e a definição foi criada por Bertrand Meyer:

"Entidades de software (classes, módulos, funções, etc.) devem estar abertas para extensão, mas fechadas para modificação."

  • Aberto pra Extensão: Você é deve ser capaz de adicionar novos comportamentos e funcionalidades ao sistema à medida que os requisitos mudam.
  • Fechada para Modificações: Ao adicionar essa nova funcionalidade, você não deve alterar o código-fonte existente que já foi estado e está funcionando

Metáfora da Tomada

Tenho um professor que comparou esse princípio com uma tomada. elétrica de parede.

  • Ela é fechada pra modificação, já que você não precisa quebrar a parede e refazer a fiação elétrica da casa toda vez que compra um novo eletrodoméstico
  • Ela é abertura para extensão, você pode instalar uma TV, ventilador ou carregador estendendo a utilidade daquela rede relétrica, apenas plugando algo novo. A interface (os buracos da tomada) permitee isso.

Exemplo prático

Vamos desenvolver um cálculo de Bônus Anual. Imagine uma classe CalculadoraDeBonus. Inicialmente, a empresa só tem "Desenvolvedores" e "Gerentes".

Exemplo Ruim (Violando OCP)

Nesta abordagem comum (mas ruim), usamos um switch ou if/else para verificar o cargo. O problema: quero adicionar dois novos cargos, diretor e estagiário. Para isso eu preciso abrir a classe e modificá-la diretamente! Isso viola o OCP

public class CalculadoraDeBonus
{
    public decimal Calcular(string cargo, decimal salario)
    {
        if (cargo == "Gerente")
        {
            return salario * 0.2m; 
        }
        else if (cargo == "Desenvolvedor")
        {
            return salario * 0.1m; 
        }

        // Se a empresa criar o cargo "Diretor" amanhã, 
        // VOU TER QUE ALTERAR ESTE ARQUIVO.
        return 0;
    }
}
Enter fullscreen mode Exit fullscreen mode

O Risco: Ao editar esse arquivo para adicionar o "Diretor", você pode sem querer apagar uma linha ou mudar a lógica do "Gerente", causando bugs em algo que já estava testado.

Solução (Aplicando OCP)

Vou trazer duas abordagens para fechar a classe para modificação e abri-las para extensão. Ambas abordagens resolvem o OCP, mas elas comunicam intenções diferentes sobre o seu sistema.

  1. Abordagem com Classe Abstrata
  2. Abordagem com Interfaces

1. Abordagem com Classe Abstrata

Use essa abrodagem quando existe uma relação forte de herança e você quer reaproveitar propriedades ou métodos

Cenário: Todo funcionário tem Nome e Salario (código compartilhado), mas o cálculo do bônus muda.

Contrato

public abstract class FuncionarioBase
{
    // Código compartilhado (Reuso)
    public string Nome { get; set; }
    public decimal Salario { get; set; }

    // Obriga as classes filhas a implementarem a lógica específica
    public abstract decimal CalcularBonus();

    // Método comum que já funciona para todos (não precisa sobrescrever)
    public string ObterCracha() => $"Funcionário: {Nome}";
}
Enter fullscreen mode Exit fullscreen mode

Aberto para extensões:

public class Gerente : FuncionarioBase
{
    public override decimal CalcularBonus()
    {
        return Salario * 0.2m;
    }
}

public class Desenvolvedor : FuncionarioBase
{
    public override decimal CalcularBonus()
    {
        return Salario * 0.1m;
    }
}
Enter fullscreen mode Exit fullscreen mode

Uso


public void Processar(FuncionarioBase funcionario) 
{
    Console.WriteLine(funcionario.CalcularBonus()); 
}
Enter fullscreen mode Exit fullscreen mode

2. Abordagem com Interface

Use esta abordagem quando o foco é o "O QUE FAZ" (Comportamento). Interfaces são contratos puros. Elas não carregam "bagagem" (estado ou variáveis) e permitem maior flexibilidade, pois uma classe pode implementar múltiplas interfaces.
Cenário: Aqui não nos importamos se é um funcionário, um prestador de serviço ou um robô. Só nos importamos se ele tem uma regra de bônus. Isso é frequentemente chamado de Strategy Pattern.

Contrato

// --- O Contrato (Interface) ---
public interface IRegraDeBonus
{
    decimal Calcular(decimal salarioBase);
}

public class BonusParaDesenvolvedor : IRegraDeBonus
{
    public decimal Calcular(decimal salarioBase)
    {
        return salarioBase * 0.1m;
    }
}

Enter fullscreen mode Exit fullscreen mode

Aberto para extensões:


// Note que não herdamos de ninguém, apenas assinamos o contrato
public class BonusParaGerente : IRegraDeBonus
{
    public decimal Calcular(decimal salarioBase)
    {
        return salarioBase * 0.2m;
    }
}
Enter fullscreen mode Exit fullscreen mode

Uso

public class CalculadoraServico
{
    // Recebemos a INTERFACE. Baixíssimo acoplamento.
    public decimal ExecutarCalculo(decimal salario, IRegraDeBonus regra)
    {
        return regra.Calcular(salario);
    }
}
Enter fullscreen mode Exit fullscreen mode

Comparação entre abordagens: Abstract Class vs. Interface

Característica Abstract Class Interface
Relacionamento "É um" (Hierarquia estrita). Um Gerente é um Funcionário. "Faz isso" (Capacidade). Essa classe sabe calcular bônus.
Reuso de Código Alto. Permite compartilhar propriedades (Nome, ID) e métodos concretos. Baixo/Nenhum. Apenas define as assinaturas dos métodos.*
Herança Limitada. C# só permite herdar de 1 classe. Se usar Abstract, você "gasta" essa cartada. Múltipla. Uma classe pode implementar 5 interfaces diferentes (IBonificavel, IAutenticavel, etc).
Acoplamento Médio. As filhas dependem da classe pai. Baixíssimo. As classes só dependem do contrato.
Quando usar no OCP? Quando você tem uma família de objetos intimamente relacionados que compartilham estrutura. Quando você quer plugar comportamentos diferentes (Strategy Pattern) ou testabilidade máxima (Mocks).

No desenvolvimento moderno de C# (.NET Core/Dependency Injection), Interfaces são usadas em 90% dos casos para garantir o OCP, pois facilitam muito os testes unitários (Mocking).

Top comments (0)