DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on

Livro Head First Design Pattern: Encapsule o que varia

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
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. 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).
  2. 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."); }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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 em Duck nem em nenhuma subclasse existente — só criar uma nova classe que implementa FlyBehavior.
  • 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)

Collapse
 
technogamerz profile image
𝐓𝐡𝐞 𝐋𝐚𝐳𝐲 𝐆𝐢𝐫𝐥 •

❤️