PrincípioIntermediário6 min de leitura9 seções1988, Data aproximada

OCP — Aberto/Fechado

Módulos devem ser abertos para extensão e fechados para modificação: adicionar comportamento novo deveria significar escrever código novo, não reabrir e alterar o que já funciona.

Em 10 segundos

Se cada requisito novo obriga a reabrir a mesma função e acrescentar um `if`, o eixo de variação está sem abstração. Com ela, comportamento novo vira classe nova — e o código que já funcionava não é tocado nem re-testado.

Antes de ter nome

Meyer pensava em herança; a leitura moderna, com interfaces e injeção de dependência, veio com a popularização do polimorfismo nos anos 1990-2000.

  • #solid
  • #extensibilidade
  • #abstracao
  • #design
Analogia

A tomada da parede

Comprar um abajur não exige chamar um eletricista para abrir a parede: você pluga na tomada. A instalação elétrica foi fechada para modificação e aberta para extensão no dia em que alguém padronizou o formato do plugue. Note que o padrão foi definido antes, e não para cada aparelho novo — é isso que o torna útil.

01Conceito

O problema

Certas funções viram ímãs de mudança: toda semana alguém acrescenta mais um caso ao mesmo switch. Elas crescem, ficam difíceis de ler e concentram o risco de regressão.

O incômodo real não é estético. Cada modificação em código testado exige re-testar, revisar e arriscar — mesmo quando o requisito novo nada tem a ver com os antigos.

O ponto sutil é que o princípio não proíbe modificar código. Ele diz que, para o eixo que você já sabe que varia, deveria existir um ponto de extensão. Correções de bug, mudanças de requisito no núcleo e refatorações continuam sendo modificação legítima.

Isso leva ao equilíbrio mais importante da prática: OCP aplicado cedo demais é adivinhação. Abstrair um eixo antes de saber que ele varia costuma produzir a abstração errada — e uma abstração errada custa mais do que o if que ela substituiu, porque agora é preciso desfazê-la.

A heurística que funciona é esperar a evidência. Na primeira ocorrência, escreva direto. Na segunda, observe. Na terceira, o eixo já se declarou e a abstração pode ser desenhada sobre fatos, não sobre suposição.

02Conceito

Como se fecha um módulo

OCP é um objetivo, não uma técnica. Vários mecanismos o alcançam, e a escolha depende de quanto de variação existe e de quem precisa estender.

Strategy é o mais direto: o comportamento variável vira um objeto injetado. É a forma usada no exemplo e serve à maioria dos casos de regra de negócio.

Template Method fecha a sequência e abre os passos — útil quando a ordem é a invariante e o miolo é o que varia.

Registro de handlers (um mapa de tipo → implementação) funciona bem quando as variantes são muitas e descobertas em tempo de execução, como em plugins.

E há a opção de não usar padrão nenhum: em linguagens funcionais, passar uma função como parâmetro alcança o mesmo fechamento com muito menos cerimônia. O princípio é sobre onde a mudança chega, não sobre quantas classes existem.

Sem o padrão × com o padrão

Aberto para extensão, fechado para modificação

  • promoção nova é uma classe nova
  • o carrinho não muda mais por causa de promoções
  • cada desconto é testável isoladamente
  • quem estende não precisa entender o resto

O custo é ter acertado o eixo: se amanhã a variação for por região em vez de por tipo de promoção, a abstração escolhida atrapalha em vez de ajudar.

O princípio move o risco: em vez de arriscar o que já funciona a cada requisito, você arrisca ter escolhido o ponto de extensão errado. Por isso a evidência antes da abstração importa tanto.
Como se organiza
  • Carrinho (fechado)depende só da interface Desconto

    • interface Descontoo ponto de extensão

  • Implementações (abertas)crescem sem tocar no carrinho

    • DescontoNatalexistente

    • DescontoFidelidadeexistente

    • DescontoCupomadicionado hoje

A fronteira é a interface. Tudo à esquerda dela permanece parado enquanto o lado direito cresce — e é isso que significa fechar sem impedir a evolução.
03Anatomia

Navegue pelas camadas

Camada 1/3O eixo de variação

Camada 1

O eixo de variação

o que realmente muda

A dimensão em que os requisitos novos chegam: tipo de desconto, formato de saída, meio de pagamento. Descobri-lo é o trabalho difícil; abstraí-lo é a parte mecânica.

No código

// três meses de commits mostraram: o que varia é a regra de desconto

Se violar: escolher o eixo errado cria uma abstração que precisa ser furada a cada requisito — pior do que não ter abstração.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

// ANTES: cada promocao nova reabre a mesma funcao
function totalRuim(valor: number, tipo: string): number {
  if (tipo === "natal") return valor * 0.9;
  if (tipo === "fidelidade") return valor - 10;
  if (tipo === "primeira-compra") return valor * 0.85;
  return valor; // e assim por diante, para sempre
}

// DEPOIS: o eixo de variacao vira abstracao
interface Desconto {
  aplicar(valor: number): number;
}

class DescontoNatal implements Desconto {
  aplicar(valor: number): number {
    return valor * 0.9;
  }
}

class DescontoFidelidade implements Desconto {
  aplicar(valor: number): number {
    return Math.max(0, valor - 10);
  }
}

class Carrinho {
  constructor(private descontos: Desconto[] = []) {}

  // esta classe nao muda mais quando surge uma promocao nova
  total(bruto: number): number {
    return this.descontos.reduce((v, d) => d.aplicar(v), bruto);
  }
}

// promocao nova = classe nova, zero alteracao no que existe
class DescontoCupom implements Desconto {
  constructor(private percentual: number) {}
  aplicar(valor: number): number {
    return valor * (1 - this.percentual);
  }
}

console.log(new Carrinho([new DescontoNatal(), new DescontoCupom(0.05)]).total(200));
06No mundo real

Casos de uso

Meios de pagamento em checkout

Cenário

O checkout aceitava cartão e boleto; em um ano entraram Pix, carteira digital e pagamento parcelado em duas formas diferentes.

Como o padrão entra

Cada meio implementa a mesma interface de cobrança e se registra num catálogo. O fluxo de checkout parou de mudar; meios novos entram sem tocar nele.

Trade-off: Meios de pagamento têm fluxos genuinamente distintos (Pix é assíncrono, cartão tem pré-autorização). Uma interface única acaba com métodos que só alguns implementam de verdade, e a abstração começa a mentir.

Exportadores de relatório

Cenário

Um relatório exportava CSV; depois vieram XLSX, PDF e um formato específico exigido por um cliente grande.

Como o padrão entra

Um exportador por formato, escolhido por um registro. Adicionar o formato do cliente foi uma classe nova, sem risco para os três já em produção.

Trade-off: Formatos com capacidades diferentes (PDF tem paginação, CSV não) forçam a interface ao menor denominador comum, e recursos específicos acabam vazando por opções extras que só um exportador entende.

Plugins de terceiros

Cenário

Uma plataforma precisa permitir que integradores adicionem comportamento sem acesso ao código-fonte nem ao ciclo de release.

Como o padrão entra

O ponto de extensão é publicado como contrato versionado; plugins são carregados em tempo de execução. Aqui o princípio deixa de ser conselho e vira requisito de arquitetura.

Trade-off: A interface vira compromisso público e praticamente imutável: qualquer mudança quebra plugins de terceiros, então erros de desenho ficam para sempre — ou exigem manter duas versões em paralelo.

Passo a passoComo fechar o módulo
  1. 1

    Esperar o eixo se declarar

    Na primeira ocorrência, escreva direto. Na segunda, observe. Na terceira, o eixo de variação já apareceu com evidência — não com adivinhação.

  2. 2

    Extrair a abstração mínima

    Uma interface pequena e estável no eixo certo. Cada método a mais aqui é trabalho para todas as implementações futuras.

  3. 3

    Depender só do contrato

    O código que orquestra chama a abstração e para de conhecer concretos. Variante nova = classe nova, zero diff no núcleo.

  4. 4

    Banir o instanceof no fechado

    Se o trecho 'fechado' pergunta o tipo concreto, a porta reabriu. A decisão por variante mora na composição, não no orquestrador.

O OCP com instanceof no miolo

Compila, roda, passa no code review. E quebra depois.

Existe interface, existem classes por promoção, o diagrama está bonito — e o carrinho ainda ramifica com instanceof. A abstração é decoração; o eixo continua aberto no lugar errado.

interface Desconto {
  aplicar(valor: number): number;
}

class DescontoNatal implements Desconto {
  aplicar(valor: number) { return valor * 0.9; }
}
class DescontoFidelidade implements Desconto {
  aplicar(valor: number) { return Math.max(0, valor - 10); }
}

class Carrinho {
  // A interface existe... e o codigo "fechado" continua aberto.
  total(bruto: number, d: Desconto): number {
    if (d instanceof DescontoNatal) return bruto * 0.9;      // furou
    if (d instanceof DescontoFidelidade) return bruto - 10;  // furou
    return d.aplicar(bruto);
  }
}

// Promocao nova = editar Carrinho de novo. O Aberto/Fechado
// virou cerimonia: classes novas + o mesmo if de sempre.

O que quebra

  • Ao adicionar promoçãoÉ preciso reabrir o Carrinho e acrescentar mais um ramo — exatamente o que o princípio prometia evitar.
  • No testeCobrir o total exige montar cada concreta real; o polimorfismo não está sendo exercitado de verdade.
  • Na revisãoO instanceof se espalha para outros métodos (frete, cashback) e cada um sai de sincronia com os demais.

A saída: Chame só d.aplicar(bruto). Se ainda for preciso escolher a concreta, o mapa mora na composição da aplicação — nunca dentro do código que deveria estar fechado.

08Cuidado

Armadilhas

  1. Abstrair antes da evidência

    Criar uma interface porque 'um dia pode variar' é a forma mais comum de complexidade acidental. Com um implementador só, a interface é adivinhação — e quando o segundo caso chega, quase nunca encaixa. Espere o terceiro caso concreto antes de fechar o eixo.

  2. Abstração furada

    O sintoma é o if (x instanceof Y) reaparecendo no código que deveria estar fechado, ou um parâmetro opcoes genérico que só uma implementação entende. Quando isso acontece, o eixo escolhido não era o certo — e insistir na abstração custa mais que voltar atrás.

  3. Fechar tudo, por precaução

    Aplicar OCP em todos os pontos produz um sistema de interfaces com um implementador cada, onde seguir uma chamada exige abrir cinco arquivos. O princípio serve aos eixos que comprovadamente variam; os demais ficam melhor escritos de forma direta.

09Decisão

Quando usar × quando evitar

Use quando

3
  • O mesmo trecho é reaberto repetidamente para acomodar variantes.
  • O eixo de variação já se mostrou em pelo menos três casos.
  • Terceiros precisam estender sem alterar seu código.

Evite quando

3
  • Você ainda não sabe o que vai variar.
  • A variação é única e sem sinal de repetição.
  • A indireção custa mais que o ramo condicional.

Testar o que ficou

3 armadilhas de OCP — Aberto/Fechado viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • sistema de plugins (ESLint, Babel)Você adiciona uma regra ou transformação escrevendo um plugin novo, sem nunca editar o núcleo — aberto para extensão, fechado para modificação.
  • middleware do ExpressCada comportamento novo (auth, log, cors) entra como uma função na cadeia; o roteador em si não muda a cada requisito que aparece.
  • extensões do VS CodeO editor ganha linguagens, temas e comandos novos por extensões instaladas, com o executável do VS Code intacto entre uma e outra.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Estende sem editar o núcleo fechado.
pagamentos.registrar("pix", novoPixStrategy);

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Um ponto de extensão a projetar e manter antes mesmo de saber se aquilo vai variar
  • Abstração escolhida no eixo errado engessa justamente onde a mudança de fato acontece

Não vale se: não há sinal de que aquele comportamento vá variar — abstrair por antecipação é complexidade paga sem retorno.

Onde isto aparece

Continue por aqui