Posts SOLID - Open Close Principle
Post
Cancel

SOLID - Open Close Principle

OCP (Open-Close Principle): Uma Abordagem Flexível

Introdução ao OCP

Open Close Principle (OCP) é a segunda letra do acrônimo SOLID (O) e se resume a “aberto para extensão, fechado para modificação”. Basicamente, significa que uma vez que o módulo (package, class, method, etc.) esteja em ambiente produtivo, não deveríamos modificá-lo diretamente, mas sim estendê-lo para adicionar novos comportamentos ou features. Na orientação a objetos, utilizamos herança, interfaces e abstrações para respeitar o OCP.

Uma classe que é modificada frequentemente pode trazer sérios prejuízos para a manutenibilidade. Além de ter o risco de alterar um comportamento existente em produção (bugs), podemos ter conflitos entre mudanças (conflitos de merges). É muito comum definir um contrato básico via interface a fim de possibilitar maior flexibilidade e extensibilidade, além de se relacionar diretamente com Dependency Inversion Principle (DIP).

OCP em Código

Um exemplo muito comum de violação do OCP em nível de classe é a criação de IF statements para controlar um fluxo que pode sofrer mudanças diretas ao adicionar novas features. Mesmo em códigos profissionais e enterprise, o exemplo abaixo é comum:

1
2
3
4
5
6
7
8
9
10
11
public class FormaPagamento {
    public boolean processarPagamento(TipoPagamentoEnum tipoPagamento, PagamentoDadosDto dados) {
        if (tipoPagamento.CREDITO.equals(tipoPagamento)) {
            // codigo para processar credito
        } else if (tipoPagamento.DEBITO.equals(tipoPagamento)) {
            // codigo para processar debito
        } else {
            throw new RuntimeException("Tipo de pagamento não localizado");
        }
    }
}

Acima, vemos que a classe FormaPagamento possui um if statement para cada forma de pagamento. Imagine que uma nova feature precisa ser adicionada a esse módulo e, nessa feature, precisamos adicionar uma nova forma de pagamento: PIX. Para essa feature, precisaríamos alterar a classe que já está em produção, sendo assim uma violação ao OCP.

Existem algumas maneiras de resolver esse problema, e o mais comum é utilizar o Design Pattern (DP) Strategy.

É aconselhável sempre desenhar a solução antes de aplicá-la em código para sua validação.

strategy_diagrama

No diagrama acima, a classe PagamentoContext utiliza a interface PagamentoStrategy. Nesse caso, ela não conhece os detalhes de implementação de pagamento, apenas chama o método processarPagamento.

Implementação em Código

Abaixo, você encontrará a implementação do padrão Strategy para resolver o problema descrito:

Interface define o contrato básico que todos os novos Planos de Pagamento precisam seguir.

Sempre que uma nova forma de pagamento é adicionada, implementamos PagamentoStrategy, respeitando o OCP.

1
2
3
public interface PagamentoStrategy<V> {
    boolean processar(V dados);
}

Exemplos de implementações para cada forma de pagamento:

1
2
3
4
5
public class PagamentoDebito implements PagamentoStrategy<DadosDebito> {
    boolean processar(DadosDebito dados) {
        // codigo para processar debito
    }
}
1
2
3
4
5
public class PagamentoCredito implements PagamentoStrategy<DadosCredito> {
    boolean processar(DadosCredito dados) {
        // codigo para processar credito
    }
}
1
2
3
4
5
public class PagamentoPix implements D<DadosPix> {
    boolean processar(DadosPix dados) {
        // codigo para processar PIX
    }
}

Também poderíamos implementar um Factory para criação dos Strategies, mas nesse caso, o Context cuidará dessa parte.

1
2
3
4
5
6
7
8
9
10
11
public class PagamentoContext<V> {
    private final PagamentoStrategy<V> strategy;

    public PagamentoContext(PagamentoStrategy<V> strategy) {
        this.strategy = strategy;
    }

    public boolean processarPagamento(V dadoPagamento) {
        return strategy.processar(dadoPagamento);
    }
}

A classe PagamentoContext centraliza o uso do strategy, trazendo alguns benefícios como, por exemplo, a possibilidade de adicionar comportamento Pre e Pós a execução do Strategy.

Exemplo de uso

1
2
3
4
5
6
7
8
9
10
11
12
package org.example;

import org.example.model.DadosCredito;

public class App {
    public static void main(String[] args) {
        DadosCredito dadosCredito = new DadosCredito();
        PagamentoContext<DadosCredito> creditoPagamentoContext = new PagamentoContext<>(new PagamentoCredito());
        boolean result = creditoPagamentoContext.processarPagamento(dadosCredito);
        System.out.println("resultado: " + result);
    }
}

Conclusão

O Open Close Principle é fundamental para criar sistemas robustos e manuteníveis. Ao seguir este princípio, garantimos que as classes existentes permaneçam fechadas para modificação, enquanto permanecem abertas para extensão. A aplicação do padrão Strategy, como demonstrado, é uma maneira eficaz de aderir ao OCP, permitindo a adição de novas funcionalidades sem alterar o código existente. Isso não apenas melhora a qualidade do código, mas também facilita a colaboração em projetos de equipe, minimizando conflitos e bugs. A abordagem orientada a objetos, quando bem aplicada, pode levar a um design de software mais limpo, eficiente e sustentável.

This post is licensed under CC BY 4.0 by the author.