SOLID – Open Closed Principle – OCP

Open Closed Principle, também conhecido como Princípio do Aberto Fechado.

OCP - Open Closed Principle

Este é o segundo princípio do SOLID e certamente o princípio mais polêmico, desconhecido e não utilizado.

Software entities (classes, modules, functions, etc.) should be open for 
extension, but closed for modification

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

Software é evolutivo, raramente um software é feito uma vez e nunca mais será modificado. Sendo assim onde esse princípio tenta chegar?

Extensibilidade

É uma das chaves da orientação a objetos, quando um novo comportamento ou funcionalidade precisar ser adicionado é esperado que as existentes sejam estendidas e e não alteradas, assim o código original permanece intacto e confiável enquanto as novas são implementadas através de extensibilidade. Criar código extensível é uma responsabilidade do desenvolvedor maduro, utilizar design duradouro para um software de boa qualidade e manutenibilidade.

Abstração

Quando aprendemos sobre orientação a objetos com certeza ouvimos sobre abstração, é ela que permite que este princípio funcione. Se um software possui abstrações bem definidas logo ele estará aberto para extensão.

Na prática

Vou usar um exemplo bem simples para podermos entender facilmente como funciona.
Observe esta classe:

public enum TipoDebito { ContaCorrente, Poupanca }

public class Debito
{
    public void Debitar(int valor, TipoDebito tipo)
    {
        if (tipo == TipoDebito.Poupanca)
        {
            // Debita Poupanca
        }
        if (tipo == TipoDebito.ContaCorrente)
        {
            // Debita ContaCorrente
        }
    }
}

É uma classe de débito em conta que valida o tipo da conta para aplicar a regra de negócio correta para conta corrente e para conta poupança. Agora vamos supor que surgiu um novo tipo de débito em conta (conta investimento), logo seria necessário modificar a classe.

Qual é o problema de um IF a mais?
Se modificarmos a classe colocando mais um IF de validação, além de ter que substituirmos esta classe na publicação da nova versão, corremos o risco de introduzir alguns bugs em uma classe que já estava funcionando.

Além de ter que testar todos os tipos de débito em conta, um bug introduzido nesta modificação não pararia apenas o débito em conta investimento mas poderia causar que todos os tipos de débitos parassem de funcionar.

Não queremos isso certo? Na verdade queremos ter o mínimo de trabalho possível e maior garantia de qualidade.

Como deveria ser?

Vamos para um exemplo de um código usando abstração para gerar extensibilidade:

public abstract class Debito
{
    public abstract void Debitar(int valor);
}

public class DebitoContaCorrente : Debito
{
    public override void Debitar(int valor)
    {
        // Debita Conta Corrente
    }
}

public class DebitoContaPoupanca : Debito
{
    public override void Debitar(int valor)
    {
        // Debita Conta Poupança
    }
}

public class DebitoContaInvestimento : Debito
{
    public override void Debitar(int valor)
    {
        // Debita Conta Investimento
    }
}

Veja que possuímos agora uma abstração bem definida, onde todas as extensões implementam suas próprias regras de negócio sem necessidade de modificar uma funcionalidade devido mudança ou inclusão de outra.

O tipo de débito em conta de investimento foi implementado sem modificar nada, usando apenas a extensão. Além de tudo o código está muito mais bonito, entendível e fácil para aplicar cobertura de testes de unidade. Vale mencionar que também está de acordo com o primeiro princípio do SOLID o SRP

Conclusão

Este princípio nos atenta para um melhor design, tornando o software mais extensível e facilitando sua evolução sem afetar a qualidade do que já está desenvolvido.

Para o uso do Open Closed Principle é muito comum utilizarmos o Strategy Pattern do GoF, prometo explicá-lo em outro momento, apenas para não tornar este exemplo muito complexo.

Referências

11 pensou em “SOLID – Open Closed Principle – OCP

  1. Mas, ao realizar herança o acoplamento aumenta, e o ultimo princípio do SOLID busca evitar isto certo ?

    Não sou muito à favor da utilização de herança de classes concretas.

  2. Eduardo , estou sempre acompanhando o seu blog e acho muito massa a iniciativa, mais cara me tira uma duvida aqui.

    Na implementação onde quer que eu vá chamar utilizar a estrutura acima eu ainda continuo usando o if

    se não como seria ?

    if(conta == ContaInvertimento){
    DebitarConta ( new DebitoContaInvestimento ());
    }
    elseif (conta == ContaPoupanca )
    {
    DebitarConta(new DebitoContaPoupanca ());
    }

    public function DebitarConta( Debito tipoDebito)
    {
    tipoDebito.Debitar( varlor )
    }

  3. Pingback: Orientação a Objeto – SOLID | RDR Blog

  4. Gostei da didática, mas pelo o que eu pude perceber, o que você fez nada mais foi que um strategy, mas acredito que o que foi explicado foi somente o conceito, mas ficou faltando o como implementar. Por exemplo agora que a relação entre Debito e as formas de debitar estão seguindo o OCP como você a implementaria? Por que se existir um DebitoService ou DebitoController ou os dois, eles ainda terão que fazer um if ou switch para saber qual implementação concreta usar. Eu gostaria muito de saber como você faria resolver essa questão.
    Mas isso é só uma dúvida minha, gostei muito do seu artigo. Parabéns!

  5. Boa tarde Eduardo , parabéns pelo seu trabalho!

    Também fiquei com a mesma dúvida:

    Na implementação onde quer que eu vá chamar utilizar a estrutura acima eu ainda continuo usando o if

    se não como seria ?

    if(conta == ContaInvertimento){
    DebitarConta ( new DebitoContaInvestimento ());
    }
    elseif (conta == ContaPoupanca )
    {
    DebitarConta(new DebitoContaPoupanca ());
    }

    public function DebitarConta( Debito tipoDebito)
    {
    tipoDebito.Debitar( varlor )
    }

  6. Respondendo à pergunta do João frade e do Rafael, vocês não precisam de if para cada tipo de classe um exemplo de implementação seria: public void DebitarValor(Debito tipoDebito, int valor) {
    tipoDebito.Debitar(valor);
    }

Os comentários estão fechados.