Introdução
No livro Head First Design Patterns ensina: não com a definição seca primeiro, mas com o problema que ele resolve. É assim que o livro faz com quase tudo — te mostra a dor antes da solução.
O princípio, em uma frase
Identifique os aspectos da sua aplicação que variam e separe-os do que permanece constante.
Ou seja: se uma parte do seu código muda com frequência (por causa de novos requisitos, novos tipos, novas regras de negócio) e outra parte é estável, não misture as duas. Isole a parte instável numa classe/interface própria, para que mudar uma coisa não quebre a outra.
O exemplo clássico do livro: o app dos patinhos (SimUDuck)
Imagine um app de simulação de lagoa com patos. Foi feito com uma classe abstrata Duck e subclasses como MallardDuck, RedheadDuck, etc. Cada uma herda quack() (grasnar) e swim() (nadar), e sobrescreve a aparência com display(). Design de herança clássico, e funciona bem — até que o chefe pede: "agora os patos precisam voar".
O Problema
A primeira ideia (ruim): adicionar fly() na classe Duck, já que herança é "reaproveitar código", certo?
Toda subclasse herda fly() automaticamente — inclusive o RubberDuck (pato de borracha), que não deveria voar. Agora você tem um pato de borracha voando pela tela. Você teria que sobrescrever fly() em cada subclasse que não deveria voar, e o mesmo aconteceria com quack() quando adicionarem um pato que não grasna.
Segunda ideia (também ruim): tirar fly() e quack() da superclasse e colocar em interfaces Flyable e Quackable, implementadas só pelos patos que precisam.
Interfaces não têm implementação (no Java clássico usado no livro). Cada subclasse que implementa Flyable precisa reescrever o próprio código de voar. Se 6 tipos de pato voam do mesmo jeito, você duplica o mesmo código 6 vezes. E se um dia mudar a lógica de voo, você corrige em 6 lugares.
Desenho do problema
!image.png
O que esse diagrama mostra
Duck define fly() diretamente na superclasse, junto com quack() e swim(). Toda subclasse herda os três automaticamente, sem escolha:
public abstract class Duck {
public void quack() { System.out.println("Quack!"); }
public void swim() { System.out.println("Nadando..."); }
public void fly() { System.out.println("Voando!"); } // implementação padrão
}
public class MallardDuck extends Duck {
// não sobrescreve nada — herda fly() e quack() como estão
}
public class RubberDuck extends Duck {
public void quack() { System.out.println("Rangido de borracha."); }
// esqueceu (ou nem pensou em) sobrescrever fly() — e agora ele voa
}
Por que isso é frágil, mesmo quando "funciona"
Repare que MallardDuck e RedheadDuck (azuis) não têm bug nenhum hoje — eles voam de verdade, então herdar fly() parece inofensivo. Mas o problema não é o bug do RubberDuck isoladamente; é que o comportamento está amarrado à árvore de herança, e essa árvore não tem como expressar "esse aqui não voa" sem sobrescrever método por método, subclasse por subclasse.
Isso significa duas coisas ruins:
-
Todo pato novo herda comportamento por padrão, e alguém precisa lembrar de corrigir manualmente quando o padrão não se aplica (como aconteceu com
RubberDuck). - Mudar como voar funciona significa mexer na superclasse, arriscando afetar todo mundo que herda dela — mesmo patos que nunca deveriam ter sido afetados.
A correção "óbvia": tornar fly() abstrato e forçar cada subclasse a implementar o próprio, parece resolver, mas cria duplicação: se 10 tipos de pato voam do mesmo jeito, você repete o mesmo código de voo 10 vezes. Mudou a física do voo? Corrige em 10 lugares.
É exatamente esse impasse — herança rígida de um lado, duplicação do outro — que o encapsulate what varies resolve, tirando o comportamento variável da árvore de herança e colocando numa família de classes própria.
Solução
A ideia central: Duck não é mais definida por herança de comportamentos, ela tem (composição) uma referência a um FlyBehavior e a um QuackBehavior. Cada família de comportamento vira sua própria hierarquia, isolada e independente da hierarquia de patos.
Como fica o código
public interface FlyBehavior {
void fly();
}
public class FlyWithWings implements FlyBehavior {
public void fly() { System.out.println("Voando!"); }
}
public class FlyNoWay implements FlyBehavior {
public void fly() { System.out.println("Não consigo voar."); }
}
E a classe Duck passa a delegar em vez de herdar diretamente o comportamento:
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void performFly() {
flyBehavior.fly(); // delega para o objeto de comportamento
}
public void performQuack() {
quackBehavior.quack();
}
}
public class MallardDuck extends Duck {
public MallardDuck() {
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
}
public class RubberDuck extends Duck {
public RubberDuck() {
flyBehavior = new FlyNoWay(); // agora é seguro: ele simplesmente não voa
quackBehavior = new MuteQuack();
}
}
Repare no que mudou:
- O que varia (como voar, como grasnar) virou uma família de classes separada, cada uma com uma única responsabilidade.
-
O que é estável (a estrutura geral de um pato — nadar, ter aparência, existir na lagoa) continua em
Duck. - Adicionar um novo tipo de voo (ex:
FlyRocketPowered) não exige tocar emDucknem em nenhuma subclasse existente — só criar uma nova classe que implementaFlyBehavior. - Você pode até trocar o comportamento em tempo de execução, já que é só uma referência a um objeto:
Isso já é o cheiro clássico de "o que varia não foi isolado".
!image.png
A ideia central: Duck não é mais definida por herança de comportamentos, ela tem (composição) uma referência a um FlyBehavior e a um QuackBehavior. Cada família de comportamento vira sua própria hierarquia, isolada e independente da hierarquia de patos.
Por que esse é o primeiro princípio do livro
O livro coloca esse princípio logo no capítulo 1 porque ele é o raciocínio de base por trás de quase todos os outros padrões. Você acabou de ver, sem saber o nome, o esqueleto do Strategy Pattern, que é exatamente isso: encapsular uma família de algoritmos intercambiáveis (aqui, os algoritmos de voar e grasnar) atrás de uma interface comum, e deixar o objeto principal delegar para eles em vez de implementar diretamente.
Esse mesmo raciocínio ("o que muda, isolo; o que não muda, mantenho estável") reaparece depois em outros padrões do livro:
- Observer encapsula "quem é notificado e como", separado do objeto que gera o evento.
- Decorator encapsula "quais responsabilidades extras" um objeto ganha, sem mexer na classe original.
- Factory Method encapsula "qual classe concreta instanciar", separado do código que usa o objeto. ****

Top comments (1)
❤️