OCP - Open-Closed Principle
Definição formal
Seção intitulada “Definição formal”O princípio é atribuído a Bertrand Meyer (Object-Oriented Software Construction, 1988) e depois reinterpretado por Robert C. Martin. A formulação clássica: “A module should be open for extension but closed for modification.”
- Aberto para extensão: o comportamento do módulo pode ser ampliado para atender a novos requisitos.
- Fechado para modificação: estender esse comportamento não exige alterar o código-fonte existente do módulo.
À primeira vista soa contraditório — como estender sem modificar? A resposta é abstração. Um módulo depende de uma interface estável (fechada); novos comportamentos chegam como novas implementações dessa interface (aberto). O código cliente não precisa ser tocado.
Martin, em Clean Architecture, eleva o OCP a princípio arquitetural: o objetivo é organizar o sistema de forma que mudanças de requisito impactem a menor quantidade possível de código, e que componentes de alto nível fiquem protegidos de mudanças em componentes de baixo nível — o que amarra o OCP ao DIP - Dependency Inversion Principle.
O problema que resolve
Seção intitulada “O problema que resolve”O anti-padrão que o OCP combate é a cadeia de if/else ou switch que cresce a cada novo caso. Cada novo tipo de desconto, cada novo meio de pagamento, cada novo formato de exportação obriga a abrir a mesma classe e adicionar um ramo. Consequências:
- Alto risco de regressão: você reabre e recompila código já validado.
- Violação implícita de SRP: a classe passa a conhecer todos os casos.
- Testes existentes precisam ser reexecutados e frequentemente reescritos.
Como identificar violações
Seção intitulada “Como identificar violações”Trade-offs e quando não aplicar dogmaticamente
Seção intitulada “Trade-offs e quando não aplicar dogmaticamente”OCP tem um custo real: abstração antecipada é uma aposta. Criar pontos de extensão exige prever quais eixos de mudança vão ocorrer. Se você abstrai o eixo errado, paga o preço da indireção sem colher o benefício — e ainda dificulta a mudança que realmente aparecer.
Não aplique OCP quando o conjunto de casos é genuinamente fechado e estável (ex.: dias da semana), ou quando o custo da indireção supera o risco de modificação em código raramente alterado.
Exemplo em Java + Spring Boot 3
Seção intitulada “Exemplo em Java + Spring Boot 3”Antes — violando OCP
Seção intitulada “Antes — violando OCP”Cada novo meio de pagamento exige abrir e modificar ProcessadorPagamento.
public enum TipoPagamento { CARTAO, PIX, BOLETO }
public record Pagamento(TipoPagamento tipo, BigDecimal valor, String documento) {}
@Servicepublic class ProcessadorPagamento {
public String processar(Pagamento pagamento) { if (pagamento.tipo() == TipoPagamento.CARTAO) { return "Cobrado " + pagamento.valor() + " no cartão"; } else if (pagamento.tipo() == TipoPagamento.PIX) { return "Gerado QR Code PIX para " + pagamento.valor(); } else if (pagamento.tipo() == TipoPagamento.BOLETO) { return "Emitido boleto de " + pagamento.valor(); } throw new IllegalArgumentException("Tipo não suportado: " + pagamento.tipo()); }}Depois — fechado para modificação, aberto para extensão
Seção intitulada “Depois — fechado para modificação, aberto para extensão”Definimos uma abstração estável e uma implementação por variação. Adicionar um novo meio de pagamento é criar uma nova classe anotada com @Component, sem tocar em nada existente.
public record Pagamento(BigDecimal valor, String documento) {}
public interface MeioPagamento { boolean suporta(String tipo); String processar(Pagamento pagamento);}@Componentpublic class PagamentoCartao implements MeioPagamento {
@Override public boolean suporta(String tipo) { return "CARTAO".equalsIgnoreCase(tipo); }
@Override public String processar(Pagamento pagamento) { return "Cobrado " + pagamento.valor() + " no cartão"; }}@Componentpublic class PagamentoPix implements MeioPagamento {
@Override public boolean suporta(String tipo) { return "PIX".equalsIgnoreCase(tipo); }
@Override public String processar(Pagamento pagamento) { return "Gerado QR Code PIX para " + pagamento.valor(); }}@Servicepublic class ProcessadorPagamento {
private final List<MeioPagamento> meios;
public ProcessadorPagamento(List<MeioPagamento> meios) { this.meios = meios; }
public String processar(String tipo, Pagamento pagamento) { return meios.stream() .filter(meio -> meio.suporta(tipo)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("Tipo não suportado: " + tipo)) .processar(pagamento); }}O Spring injeta automaticamente todas as implementações de MeioPagamento na List. Um novo PagamentoBoleto implements MeioPagamento entra em funcionamento apenas por existir — ProcessadorPagamento permanece fechado.
Diagrama
Seção intitulada “Diagrama”classDiagram
class ProcessadorPagamento {
-List~MeioPagamento~ meios
+processar(String, Pagamento)
}
class MeioPagamento {
<<interface>>
+suporta(String) boolean
+processar(Pagamento) String
}
class PagamentoCartao {
+suporta(String) boolean
+processar(Pagamento) String
}
class PagamentoPix {
+suporta(String) boolean
+processar(Pagamento) String
}
class PagamentoBoleto {
+suporta(String) boolean
+processar(Pagamento) String
}
ProcessadorPagamento --> MeioPagamento
MeioPagamento <|.. PagamentoCartao
MeioPagamento <|.. PagamentoPix
MeioPagamento <|.. PagamentoBoleto
A seta de ProcessadorPagamento aponta apenas para a abstração. As implementações novas dependem da abstração, nunca o contrário.
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”Relações
Seção intitulada “Relações”- Faz parte de: SOLID
- Depende de: LSP - Liskov Substitution Principle (a extensão por polimorfismo só é segura se os subtipos honram o contrato).
- Habilitado por: DIP - Dependency Inversion Principle (dependa da abstração) e ISP - Interface Segregation Principle (abstrações enxutas).
- É o objetivo protegido pela A Regra da Dependência em Clean Architecture.
- O “Depois” do exemplo é o padrão Strategy; o cheiro do “Antes” está mapeado em Code Smells e SOLID (Replace Conditional with Polymorphism, ver Técnicas de Refactoring).
- Índice: Home
Perguntas de revisão
Seção intitulada “Perguntas de revisão”- Como resolver a aparente contradição entre “aberto para extensão” e “fechado para modificação”?
Resposta
Por meio de abstração polimórfica: o cliente depende de uma interface estável (fechada), e novos comportamentos entram como novas implementações dessa interface (aberto), sem editar o cliente.
- Qual é a heurística de Uncle Bob para não cair em super-engenharia ao aplicar OCP?
Resposta
“Fool me once”: escreva concreto na primeira vez; introduza a abstração quando a segunda variação surgir, pois só então você conhece o eixo real de variação. Antecipar demais viola YAGNI.
- Como o Spring viabiliza o OCP no exemplo dos meios de pagamento?
Resposta
Injetando automaticamente todas as implementações da interface MeioPagamento como uma List. Uma nova implementação anotada com @Component passa a funcionar sem alterar ProcessadorPagamento.
- Por que OCP depende de LSP?
Resposta
Porque a extensão via polimorfismo só é segura se cada subtipo puder substituir a abstração sem quebrar o contrato esperado pelo cliente. Se um subtipo viola LSP, o cliente “fechado” passa a precisar de tratamento especial, quebrando o OCP.