O caixa eletrônico, não o cofre aberto
Você não mete a mão no cofre do banco: deposita, saca, consulta. O banco muda o layout do cofre sem te avisar. Encapsulamento é isso — operações públicas, estado fora do alcance. Abrir o campo saldo é deixar o cofre na rua.
Contrato, não paredes
O ponto não é ‘tornar private’. É decidir o que o resto do sistema pode assumir. Se o único método é setSaldo, você encapsulou o nome do campo e vazou a liberdade de corromper o valor.
Abstração caminha junto: o consumidor pensa em ‘depositar’, não em ‘somar no array de lançamentos’. Quando a implementação muda e o contrato aguenta, o encapsulamento pagou a conta.
Estado atrás do contrato
- Campos privados / internos ao módulo
- Operações que validam e preservam regras
- Representação livre para evoluir
Um pouco mais de API — em troca de um objeto que ainda faz sentido daqui a um ano.
- 1
Esconder o estado
Campos viram privados / internos ao módulo — o consumidor não escreve direto.
- 2
Publicar operações
Métodos com nomes de domínio (`depositar`, `pagar`) validam e preservam invariantes.
- 3
Expor só o necessário
Leitura via getter ou cópia; nunca a coleção mutável interna.
- 4
Evoluir por dentro
Com o contrato estável, a representação pode mudar sem quebrar quem consome.
Como isso aparece na prática
Pedido com status público
Cenário
Um pedido.status = 'pago' aparecia em três services. Alguém setou 'cancelado' depois do pagamento sem estornar.
Como o padrão entra
O status virou transição por métodos (pagar, cancelar) que checam o estado atual antes de mudar.
Trade-off: Mais métodos no agregado. Em troca, transição ilegal deixa de ser possível por atribuição direta.
Config espalhada em objeto mutável
Cenário
Um singleton de config era mutado em testes e em runtime — um teste ‘consertava’ o valor e o próximo falhava.
Como o padrão entra
Config virou imutável na borda; mudanças passam por rebuild ou por um serviço com API explícita.
Trade-off: Menos atalho nos testes. Em troca, o estado global parou de vazar entre casos.
Onde isto dá errado
Getter/setter que só espelha o campo
Se
setXaceita qualquer valor egetXdevolve o campo, você não encapsulou — só mudou a sintaxe do vazamento.Expor a coleção interna
getItens()que devolve a lista mutável interna permitepedido.getItens().clear()por trás das costas do objeto.Encapsular demais e virar god object
Esconder estado não autoriza um único objeto a saber de tudo. SRP continua valendo — encapsule por responsabilidade, não por tamanho.
O jeito errado
Compila, roda, passa no code review. E quebra depois.
Campo público (ou getter que devolve a lista interna) e a invariante espalhada em quem usa.
class Pedido {
status: "rascunho" | "pago" | "cancelado" = "rascunho";
itens: string[] = [];
}
// Em qualquer lugar do sistema:
pedido.status = "pago";
pedido.itens.push("x"); // ou .clear() — sem regraO que quebra
- alguém seta status ilegalo domínio aceita estados que o negócio não existe
- a lista interna vazamutação silenciosa sem passar pelo agregado
A saída: Transições por método (pagar, adicionarItem) e coleções expostas só como cópia ou leitura.
Do campo público ao contrato
// SEM encapsulamento: o estado é público e qualquer um mexe.
class ContaRuim {
saldo = 0;
}
const c = new ContaRuim();
c.saldo = -999; // ninguém impede — invariante morta
// COM encapsulamento: o estado mora atrás de um contrato.
class Conta {
#saldo = 0;
depositar(v: number) {
if (v <= 0) throw new Error("valor inválido");
this.#saldo += v;
}
get saldo() { return this.#saldo; } // lê, não escreve à vontade
}
// Quem usa Conta não sabe — nem precisa saber — como o saldo é guardado.class Conta:
def __init__(self):
self._saldo = 0 # convenção: interno
def depositar(self, v: float) -> None:
if v <= 0:
raise ValueError("valor inválido")
self._saldo += v
@property
def saldo(self) -> float:
return self._saldo
# O "_" sinaliza: não toque direto — use o contrato.public final class Conta {
private int saldo;
public void depositar(int v) {
if (v <= 0) throw new IllegalArgumentException("valor");
saldo += v;
}
public int getSaldo() { return saldo; }
}
// private + métodos públicos = contrato; o campo não vaza.Quando usar × quando evitar
Use quando
3- Há invariante a proteger.
- A representação interna pode mudar.
- Fronteira de módulo: API pública × detalhe privado.
Evite quando
2- DTO sem regra — o formato já é o contrato.
- Getter/setter 1:1 sem validação (teatro de encapsulamento).
Testar o que ficou
3 armadilhas de Encapsulamento 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.
campos private / #privadoA linguagem força o acesso pelo método — o estado deixa de ser propriedade pública do chamador.getters sem setter cegoLer o saldo é ok; escrever à vontade não. O contrato decide o que pode mudar e como.módulo ES que não exportaO que não sai do arquivo é interno: o importador só vê a API exportada — encapsulamento de módulo.
O padrão em uma linha
Sem nome de domínio, sem cerimônia. Só a ideia.
class Conta { #saldo = 0; depositar(v: number) { this.#saldo += v; } }O que ele cobra
1 salto a mais entre a chamada e o efeito.
- Mais métodos (e nomes) do que expor o campo direto
- Contrato mal desenhado vira getter/setter que só empacota o vazamento
Não vale se: o dado é realmente um DTO sem invariante — aí um record/objeto plano é o contrato, não um teatro de private.
Onde isto aparece
Continue por aqui
- PolimorfismoUm mesmo contrato, várias implementações: o código chama a abstração e o comportamento concreto chega em runtime. Herança é um caminho para isso — não o único. Sem polimorfismo, cada variação vira `if/switch` no cliente; com ele, novas variantes entram sem reabrir quem chama.
- Composição sobre herançaHerança promete reúso e entrega acoplamento: a subclasse herda tudo do pai, inclusive o que não queria, e a árvore engessa numa dimensão só. Compor comportamento a partir de peças encaixáveis dá o mesmo reúso sem amarrar quem usa a quem fornece — por isso o conselho antigo de preferir composição, e só herdar quando a relação 'é um' é honesta.
- SRP — Responsabilidade ÚnicaUm módulo deve ter apenas uma razão para mudar — ou seja, deve responder a um único interessado do negócio, e não acumular decisões que evoluem por motivos diferentes.
- Value ObjectNem tudo no domínio tem identidade. Dez reais é dez reais; um CEP é um CEP — não existe 'este' e 'aquele', existe o valor. Um Value Object é um tipo definido só pelo seu conteúdo, imutável e com igualdade por valor, que carrega as regras daquele conceito em vez de deixá-las espalhadas sobre um number ou string cru. É o antídoto da obsessão por primitivos.