← tópicos

Específicos · Engenharia de Software

GoF: criacionais, estruturais, comportamentais

Leia primeiro o que o item descreve, decida a família (cria objeto, monta estrutura, distribui comportamento) e só então confira o nome assinado — o molde dominante da banca é descrever um padrão…

Altíssima29 itens no tópico

A ideia que organiza o assunto

Um padrão de projeto não é código, nem biblioteca, nem framework. É a descrição de um problema recorrente e do arranjo de classes e objetos que costuma resolvê-lo, escrita em nível alto o bastante para ser reimplementada de outro jeito em cada sistema. Por isso duas aplicações que usam o mesmo padrão não têm o mesmo código: têm a mesma solução de projeto.

Os vinte e três padrões do catálogo GoF se dividem por o que eles fazem, e essa divisão é o primeiro filtro de qualquer item:

Daí sai o método de prova deste tópico, e ele é mecânico: leia a descrição primeiro, decida que padrão é aquele, e só depois olhe o nome que o item escreveu. O molde dominante da CEBRASPE aqui é definir um padrão com precisão de manual e assinar o nome de outro — “o padrão composite atribui responsabilidades adicionais a um objeto dinamicamente” é a definição exata do decorator. Quem julga pelo nome cai; quem julga pela descrição não precisa decorar item nenhum.

Por que se usa (e o que custa)

O ganho é reuso de projeto, não de código: o padrão já resolveu, em outro sistema e em outra linguagem, a tensão entre o que hoje funciona e o que amanhã vai mudar. Ele também dá vocabulário — dizer “aqui entra um observer” substitui meia hora de desenho — e ajuda a especificar interfaces, identificando os elementos-chave e o que trafega entre os objetos.

O preço é indireção. Todo padrão troca uma dependência direta por uma camada a mais: mais classes, mais delegação, mais arquivos para entender antes de achar onde o trabalho de fato acontece. O factory method resolve a parametrização por classe de produto, mas cobra uma subclasse nova só para trocar o produto. O decorator multiplica objetos pequenos e parecidos. Aplicar padrão onde não há o problema que ele resolve é o caso clássico de solução procurando problema — e é por isso que o padrão exige contexto entendido: sem conhecer o problema recorrente e as forças em jogo, não há como escolher.

Como funciona

Os dois princípios que sustentam o catálogo. O primeiro é programar para uma interface, não para uma implementação: o cliente declara o tipo abstrato e nunca depende da classe concreta que vai chegar. O segundo é favorecer a composição de objetos sobre a herança de classe: herança fixa o comportamento em tempo de compilação e expõe o pai ao filho; composição troca a peça em tempo de execução. Quase todo padrão é uma aplicação de um dos dois — e a decomposição do sistema em objetos é influenciada por encapsulamento, granularidade, dependências, flexibilidade e desempenho, não por elegância apenas.

Os criacionais. O singleton garante uma instância única e um ponto global de acesso: depois que o objeto existe, o sistema não pode criar outro daquela classe. O factory method define uma operação de criação e deixa a subclasse escolher a classe concreta do produto. A abstract factory vai um nível acima: uma interface para criar famílias de objetos relacionados, sem nomear as classes concretas. O builder separa a construção de um objeto complexo da sua representação, de modo que o mesmo processo de construção produza representações diferentes. O prototype especifica os tipos de objetos a criar usando uma instância protótipo e cria novos por cópia dela — e a cópia aproveita o estado do original, que é justamente o ponto do padrão.

Os estruturais. O adapter converte a interface de uma classe em outra que o cliente espera, fazendo trabalhar junto o que não trabalharia. O bridge separa uma abstração da sua implementação, para que as duas variem de forma independente. O composite monta objetos em árvores parte-todo e permite ao cliente tratar objetos individuais e composições de maneira uniforme. O decorator acrescenta responsabilidades a um objeto dinamicamente, como alternativa flexível à herança. A facade oferece uma interface única e simplificada para um conjunto de interfaces de um subsistema. O flyweight compartilha estado intrínseco para suportar muitos objetos pequenos com pouco custo de memória. O proxy fornece um objeto substituto que controla o acesso a outro objeto e a ele faz referência.

Os comportamentais. O chain of responsibility desacopla remetente e receptor passando a solicitação por uma cadeia de receptores potenciais até que um a trate. O command encapsula uma requisição como objeto, o que permite parametrizar clientes, enfileirar, registrar e desfazer operações, sem que o invocador saiba como a operação é executada. O iterator dá acesso sequencial aos elementos de uma coleção sem expor sua representação interna, protegendo a coleção do acesso direto. O memento captura e externaliza o estado interno de um objeto sem violar o encapsulamento, para que ele possa ser restaurado depois. O observer define dependência um-para-muitos: mudou o estado do objeto observado, todos os dependentes são notificados e atualizados — é a mecânica do MVC entre modelo e visões. O strategy encapsula uma família de algoritmos e os torna intercambiáveis, deixando o algoritmo variar independentemente dos clientes que o usam; o efeito colateral mais cobrado é remover lógica condicional da classe. Completam o grupo state, template method, visitor, mediator e interpreter.

O que decide os itens

Padrão → família → o que resolve. É a tabela que resolve a maioria dos itens:

padrãofamíliaresolve
singletoncriacionaluma única instância e ponto global de acesso
factory methodcriacionalsubclasse decide qual classe de produto instanciar
abstract factorycriacionalcriar famílias de objetos relacionados
buildercriacionalseparar construção de representação
prototypecriacionalcriar por cópia de uma instância protótipo
adapterestruturalconverter uma interface na que o cliente espera
bridgeestruturalseparar abstração de implementação
compositeestruturaltratar parte e todo de forma uniforme (árvore)
decoratorestruturalacrescentar responsabilidade dinamicamente
facadeestruturalinterface única e simplificada para um subsistema
flyweightestruturalcompartilhar objetos para economizar memória
proxyestruturalsubstituto que controla o acesso a outro objeto
chain of responsibilitycomportamentalpassar a requisição por uma cadeia de receptores
commandcomportamentalrequisição como objeto: enfileirar, registrar, desfazer
iteratorcomportamentalpercorrer a coleção sem expor a representação
mementocomportamentalguardar e restaurar estado sem violar encapsulamento
observercomportamentalnotificar dependentes quando o estado muda
strategycomportamentaltrocar o algoritmo sem tocar no cliente
statecomportamentalmudar o comportamento quando o estado interno muda
template methodcomportamentalesqueleto do algoritmo na superclasse, passos na subclasse

Pares que a banca troca o tempo todo:

×quem é quem
adapter × bridgeadapter converte interface existente; bridge separa abstração de implementação desde o projeto
adapter × strategyvariar algoritmo independentemente do cliente é strategy
proxy × bridgeobjeto substituto que referencia outro é proxy
composite × decoratoruniformidade parte-todo é composite; responsabilidade adicional dinâmica é decorator
composite × facadetratar objeto e composição de modo uniforme é composite, não facade
facade × builderinterface simplificada é facade; construção × representação é builder
command × chain of responsibilitycadeia de receptores é chain; requisição como objeto é command
singleton × prototypeinstância única é singleton; instância protótipo copiada é prototype
iterator × decoratoralternativa flexível à herança é decorator; acesso sequencial é iterator
flyweight × adapterexcesso de instâncias e consumo de memória é flyweight

Definição × implementação. Padrão é descrição de solução em alto nível, não framework, não biblioteca, não código pronto. Dois sistemas com o mesmo padrão têm o mesmo desenho, não o mesmo código — o padrão não unifica código.

Padrão exige contexto. A escolha depende de conhecer o problema recorrente, as forças e as consequências. “Não deu tempo de entender o contexto, então aplique um padrão” é o oposto do que o catálogo prescreve.

Padrões e interfaces. Os padrões ajudam a especificar interfaces: identificam seus elementos-chave e os dados que trafegam entre objetos. A essência não está na implementação nem nos algoritmos.

Números que caem

padrões do catálogo GoF23
famílias (propósito)3: criacional, estrutural, comportamental
padrões criacionais5: singleton, factory method, abstract factory, builder, prototype
padrões estruturais7: adapter, bridge, composite, decorator, facade, flyweight, proxy
padrões comportamentais11
escopo (segunda classificação do GoF)2: de classe e de objeto
instâncias garantidas pelo singleton1

Como a CEBRASPE derruba você aqui

Os 29 itens do tópico estão explicados. Sobre os 15 de gabarito Errado: troca de termo 60% (9 itens), inversão 27% (4), relação causal e escopo ampliado com 7% cada. Catorze itens são Certo.

Duas coisas que a contagem revelou sobre o recorte, e que mudam o que vale a pena decorar. A primeira: um quarto do tópico não é sobre padrão nenhum. Sete dos 29 itens discutem o que um padrão é — se contribui para especificar interfaces, se é código pronto, se unifica código entre aplicações, quando se deve adotá-lo, quais são as três famílias, o princípio de programar para a interface — e quatro desses sete são de gabarito Errado. É a fatia mais rentável do tópico por item estudado. A segunda: dos 23 padrões do catálogo, cinco não aparecem uma única vez — state, template method, visitor, mediator e interpreter. E decorator e proxy, que são dos mais cobrados, nunca aparecem escritos no enunciado: eles só comparecem como a resposta certa por trás do nome de outro padrão. Quem só decora nome errado de padrão perde metade do jogo.

A definição do catálogo com o nome de outro padrão carimbado nela — 60%, a família absolutamente dominante. A frase descreve um padrão com precisão de manual e assina outro. O singleton cujo objetivo seria “especificar os tipos de objetos a partir de uma instância de protótipo” (é prototype); o bridge que “fornece um objeto substituto, que faz referência a outro objeto” (é proxy); o composite que “atribui responsabilidades adicionais a um objeto dinamicamente” (é decorator); o facade que “separa a construção de um objeto complexo da sua representação” (é builder); o adapter cujo propósito seria “separar uma abstração de sua implementação” (é bridge) e, em outro item, no qual “um algoritmo pode variar independentemente dos clientes que o utilizam” (é strategy); o command definido como “passar uma requisição entre uma lista ou objetos encadeados” (é chain of responsibility); o iterator como “forma flexível de uso de herança para estender uma funcionalidade” (é decorator); e o “tratar, de maneira uniforme, objetos individuais e suas composições” atribuído ao facadecomposite).

E aqui está o dado que muda a tática: em 6 desses 9 itens o nome errado pertence à mesma família da descrição. Dizer “o padrão de projeto estrutural bridge” e descrever o proxy é acertar a família e errar o padrão; o mesmo vale para adapter/bridge, composite/decorator, facade/composite, singleton/prototype e command/chain. Decidir a família é o primeiro filtro, mas ele sozinho resolve apenas 3 dos 9. O que resolve todos é a palavra de assinatura da definição, e elas são poucas e fixas: protótipo → prototype; dinamicamente junto de responsabilidade adicional → decorator; encadeado, cadeia de receptores → chain of responsibility; varia independentemente dos clientes → strategy; objeto substituto → proxy; parte-todo, individuais e composições → composite; alternativa flexível à herança → decorator; construção × representação → builder. Defesa: leia a oração que define, escreva você o nome, e só então olhe a assinatura do item.

A propriedade definidora virada do avesso — 27%. Quatro itens, e em três deles o padrão nomeado está certo e é a característica que lhe dá sentido que foi negada ou girada. O prototype que cria objetos “sem aproveitar o estado do objeto copiado”, quando aproveitar o estado é o padrão inteiro — pergunte-se: se isso fosse verdade, para que serviria o padrão? Os design patterns que “não se destinam a contribuir com especificação de interfaces de objetos, pois a essência dos padrões está nos algoritmos e na implementação”, quando é exatamente ao contrário: a essência é o arranjo de classes e a intenção, e eles ajudam sim a especificar interfaces. “Trata-se de um framework ou código pronto, e não de uma definição de alto nível de como um problema comum pode ser solucionado”, com as duas pontas do “não X, mas Y” simplesmente trocadas de lugar. E a recomendação girada entre dois padrões: “o padrão Adapter é mais apropriado que o padrão Flyweight” para excesso de instâncias consumindo memória, quando é o flyweight que resolve esse problema. Defesa: em item que compara dois padrões com “mais apropriado que”, monte a ordem certa antes de ler a do enunciado — a banca costuma apenas girar os dois nomes.

O benefício esticado até o código — 7%. “O uso de design patterns leva à unificação dos códigos utilizados em diferentes aplicações que utilizem o mesmo padrão.” O que se reaproveita é a solução de projeto; duas aplicações que usam observer terminam com o mesmo desenho de colaboração e com código inteiramente distinto. Defesa: padrão padroniza desenho e vocabulário, nunca implementação.

A condição de uso invertida numa oração explicativa — 7%. “Quando o contexto não estiver claro e não houver tempo para entendê-lo, devem ser adotados padrões de projeto constantes da literatura da área.” O porquê soa razoável e carrega a mentira: padrão descreve a solução de um problema recorrente dentro de um contexto compreendido, e escolhê-lo exige entender esse contexto. Defesa: padrão, framework ou norma oferecidos como substituto da análise do problema é sempre item errado.

Erros clássicos

Julgar pelo nome antes de ler a descrição. É o erro que produz a maior parte das quedas. Leia a oração que define, decida que padrão é, e só então compare com a assinatura.

Confundir adapter, bridge e proxy. Os três são estruturais e envolvem um objeto em outro, mas a intenção difere: adapter converte uma interface que já existe e não batia; bridge é projetado de antemão para que abstração e implementação variem em paralelo; proxy mantém a mesma interface e controla o acesso.

Achar que decorator e composite são a mesma coisa. Ambos compõem objetos recursivamente. Composite existe para tratar folha e nó do mesmo jeito; decorator existe para empilhar responsabilidades sobre um objeto em tempo de execução.

Trocar factory method por abstract factory. Factory method é uma operação que a subclasse sobrescreve para decidir um produto; abstract factory é um objeto que cria uma família inteira de produtos compatíveis.

Esquecer o custo do factory method. O próprio GoF registra a desvantagem: pode ser preciso criar uma subclasse nova apenas para mudar a classe do produto.

Tratar singleton como boa prática neutra. Instância única é estado global compartilhado: dificulta substituir a dependência por dublê e quebra o isolamento dos testes de unidade.

Confundir flyweight com cache ou com pool genérico. O flyweight separa estado intrínseco (compartilhável, guardado no objeto) de extrínseco (passado pelo cliente a cada operação). É essa separação que permite muitos objetos com pouca memória.

Praticar29 itens