Revisão Geral de Design Patterns
Introdução
Seção intitulada “Introdução”Este tópico reúne uma revisão geral do que foi estudado até aqui em Design Patterns. A ideia não é substituir os materiais individuais de cada padrão, mas consolidar a visão do conjunto para facilitar revisão, comparação e escolha consciente de soluções de projeto.
Ao longo do conteúdo, retomaremos:
- o que são Design Patterns
- a categorização GoF
- os padrões criacionais estudados
- os padrões estruturais estudados
- uma síntese comparativa por categoria
O foco aqui é ajudar a responder perguntas como:
- qual problema cada padrão tenta resolver?
- em que contexto ele costuma ser útil?
- qual estrutura básica de implementação ele sugere?
- quais ganhos e custos aparecem ao adotá-lo?
O que é Design Patterns
Seção intitulada “O que é Design Patterns”Design Patterns são soluções recorrentes e reutilizáveis para problemas comuns de projeto de software. Eles não são trechos prontos de código para copiar e colar, mas sim modelos de organização que ajudam a estruturar classes, objetos e responsabilidades.
Em termos práticos, padrões de projeto ajudam a:
- reduzir acoplamento desnecessário
- organizar melhor criação, composição e colaboração entre objetos
- tornar o código mais legível e extensível
- criar um vocabulário comum entre desenvolvedores
Ao dizer que um trecho de código usa Adapter, Factory Method ou Decorator, por exemplo, o time consegue comunicar com rapidez a intenção do design adotado.
Gamma Categorization
Seção intitulada “Gamma Categorization”Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, a Gang of Four (GoF), organizaram os padrões clássicos em três grandes categorias. Essa categorização não existe por formalidade; ela ajuda a entender qual dimensão do problema cada padrão ataca.
Padrões Criacionais
Seção intitulada “Padrões Criacionais”Os padrões criacionais tratam do processo de criação de objetos. Eles buscam responder perguntas como:
- quem deve criar o objeto?
- quando o objeto deve ser criado?
- como evitar dependência direta de classes concretas?
- como representar construções complexas ou famílias de objetos?
Em geral, são úteis quando o problema principal está no instanciamento, na configuração inicial ou no controle da criação.
Padrões Estruturais
Seção intitulada “Padrões Estruturais”Os padrões estruturais tratam da composição de classes e objetos. Eles ajudam a organizar relações entre partes do sistema para que estruturas maiores permaneçam compreensíveis, flexíveis e reutilizáveis.
Em geral, respondem perguntas como:
- como integrar interfaces incompatíveis?
- como compor objetos sem explodir a hierarquia de classes?
- como simplificar o acesso a subsistemas?
- como compartilhar estrutura ou comportamento sem duplicação excessiva?
Padrões Comportamentais
Seção intitulada “Padrões Comportamentais”Os padrões comportamentais focam na interação entre objetos e na distribuição de responsabilidades. Eles procuram organizar fluxos, protocolos de comunicação e variações de comportamento.
Embora esta categoria ainda não tenha sido aprofundada em tópicos específicos até aqui, ela completa a visão GoF por lidar com perguntas como:
- como objetos colaboram?
- como encapsular algoritmos, comandos ou estados?
- como notificar mudanças e coordenar ações?
Visão Geral do Conteúdo Estudado
Seção intitulada “Visão Geral do Conteúdo Estudado”Até este ponto da disciplina, foram abordados:
- fundamentos introdutórios sobre Design Patterns
- princípios SOLID como apoio ao bom design
- 5 padrões criacionais
- 7 padrões estruturais
Isso significa que já existe uma base suficiente para perceber um ponto importante: padrões não são soluções isoladas; eles formam um repertório complementar. Em muitos sistemas reais, um mesmo módulo combina mais de um padrão ao mesmo tempo.
Padrões Criacionais
Seção intitulada “Padrões Criacionais”Os padrões criacionais vistos até aqui foram:
- Singleton
- Builder
- Factory Method
- Abstract Factory
- Prototype
Singleton
Seção intitulada “Singleton”O Singleton garante que uma classe possua apenas uma instância e oferece um ponto global de acesso a ela.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- um recurso realmente deve ser único na aplicação
- faz sentido centralizar acesso a configuração, log ou cache compartilhado
- a unicidade faz parte do requisito, e não apenas da conveniência
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class Singleton {
-instancia: Singleton
-Singleton()
+getInstancia() Singleton
+operacao()
}
- garante instância única
- centraliza acesso a recurso compartilhado
- pode adiar criação até o primeiro uso
Contras
Seção intitulada “Contras”- cria dependência global
- dificulta testes e substituição por mocks
- pode esconder acoplamento excessivo
Builder
Seção intitulada “Builder”O Builder separa a construção de um objeto complexo da sua representação, permitindo montar a solução passo a passo.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- o objeto possui muitos parâmetros ou combinações possíveis
- a criação precisa acontecer em etapas
- você quer melhorar legibilidade e evitar construtores telescópicos
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class Cliente
class Director {
+construir()
}
class Builder {
<<interface>>
+reset()
+construirParteA()
+construirParteB()
+obterResultado()
}
class ConcreteBuilder {
+construirParteA()
+construirParteB()
+obterResultado()
}
class Produto
Cliente --> Director
Director --> Builder
ConcreteBuilder ..|> Builder
ConcreteBuilder --> Produto
- melhora legibilidade da criação
- facilita variações de montagem
- reduz objetos inconsistentes durante construção
Contras
Seção intitulada “Contras”- adiciona mais classes e passos
- pode ser exagero para objetos simples
- director nem sempre compensa em cenários pequenos
Factory Method
Seção intitulada “Factory Method”O Factory Method define um método de criação e delega às subclasses a decisão sobre qual produto concreto instanciar.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- o código cliente deve depender de abstrações, não de concretos
- subclasses precisam escolher diferentes implementações
- a criação varia, mas o fluxo principal permanece semelhante
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class Product {
<<interface>>
+operacao()
}
class ConcreteProductA
class ConcreteProductB
class Creator {
+algoritmoPrincipal()
#factoryMethod() Product
}
class ConcreteCreatorA {
+factoryMethod() Product
}
class ConcreteCreatorB {
+factoryMethod() Product
}
ConcreteProductA ..|> Product
ConcreteProductB ..|> Product
ConcreteCreatorA --|> Creator
ConcreteCreatorB --|> Creator
Creator --> Product
- reduz acoplamento com classes concretas
- facilita extensão por novas subclasses
- mantém o fluxo principal reutilizável
Contras
Seção intitulada “Contras”- aumenta a hierarquia de classes
- pode espalhar a lógica de criação em subclasses
- é menos direto do que uma instanciação simples
Abstract Factory
Seção intitulada “Abstract Factory”O Abstract Factory cria famílias de objetos relacionados sem expor diretamente suas classes concretas.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- há famílias coerentes de produtos
- o sistema precisa evitar mistura entre variantes incompatíveis
- o cliente deve trocar a família inteira sem reescrever o código
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class AbstractFactory {
<<interface>>
+criarProdutoA() ProdutoA
+criarProdutoB() ProdutoB
}
class ConcreteFactory1
class ConcreteFactory2
class ProdutoA {
<<interface>>
}
class ProdutoB {
<<interface>>
}
class ProdutoA1
class ProdutoA2
class ProdutoB1
class ProdutoB2
class Cliente
ConcreteFactory1 ..|> AbstractFactory
ConcreteFactory2 ..|> AbstractFactory
ProdutoA1 ..|> ProdutoA
ProdutoA2 ..|> ProdutoA
ProdutoB1 ..|> ProdutoB
ProdutoB2 ..|> ProdutoB
Cliente --> AbstractFactory
- garante coerência entre produtos da mesma família
- reduz dependência do cliente em relação às classes concretas
- facilita troca de plataforma, tema ou ambiente
Contras
Seção intitulada “Contras”- adicionar novo tipo de produto é custoso
- aumenta o número de interfaces e fábricas
- pode ser pesado para cenários com poucas variações
Prototype
Seção intitulada “Prototype”O Prototype cria novos objetos por cópia de instâncias existentes, reduzindo dependência do cliente em relação ao processo de criação detalhado.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- objetos semelhantes são criados repetidamente
- a configuração inicial é trabalhosa ou cara
- faz sentido manter modelos prontos para clonagem
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class Prototype {
<<interface>>
+clonar() Prototype
}
class ConcretePrototype {
+clonar() Prototype
}
class Cliente
ConcretePrototype ..|> Prototype
Cliente --> Prototype
- evita reconstrução repetitiva de objetos complexos
- reduz acoplamento com classes concretas
- facilita criar variações a partir de modelos base
Contras
Seção intitulada “Contras”- exige cuidado com cópia rasa e profunda
- pode compartilhar estado mutável sem intenção
- a lógica de clonagem pode ficar delicada
Síntese dos Padrões Criacionais
Seção intitulada “Síntese dos Padrões Criacionais”Os padrões criacionais estudados resolvem problemas diferentes, mas giram em torno da mesma pergunta: como criar objetos de forma mais controlada, flexível e compreensível?
Singletoncontrola unicidadeBuilderorganiza construção passo a passoFactory Methoddesloca a decisão de criação para subclassesAbstract Factorycria famílias coerentesPrototypereutiliza objetos-base por cópia
Tabela comparativa dos padrões criacionais
Seção intitulada “Tabela comparativa dos padrões criacionais”| Padrão | Ideia central | Aplicabilidade | Estrutura básica | Prós | Contras |
|---|---|---|---|---|---|
| Singleton | Garantir uma única instância | Configuração global, log, recursos únicos | Classe com construtor privado e acesso estático | Controle central e unicidade | Estado global e testes mais difíceis |
| Builder | Construir objeto em etapas | Objetos complexos ou com muitos opcionais | Cliente/Director, Builder, Produto | Legibilidade e flexibilidade de montagem | Mais classes e cerimônia |
| Factory Method | Delegar criação a subclasses | Variação de produtos com fluxo comum | Creator, ConcreteCreator, Product | Menor acoplamento com concretos | Hierarquia maior |
| Abstract Factory | Criar famílias de objetos relacionados | Temas, plataformas, ambientes compatíveis | Fábrica abstrata, fábricas concretas, produtos abstratos | Coerência entre famílias | Difícil adicionar novo produto |
| Prototype | Clonar objetos existentes | Modelos pré-configurados e criação cara | Protótipo com método de clonagem | Reuso de configuração base | Complexidade de cópia |
Padrões Estruturais
Seção intitulada “Padrões Estruturais”Os padrões estruturais vistos até aqui foram:
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Proxy
- Flyweight
Adapter
Seção intitulada “Adapter”O Adapter traduz uma interface incompatível para outra esperada pelo cliente.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- você precisa integrar código legado ou biblioteca de terceiros
- o cliente já depende de um contrato que não deve mudar
- a incompatibilidade está na interface ou no formato de uso
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class Cliente
class Target {
<<interface>>
+requisicao()
}
class Adapter {
-adaptee: Adaptee
+requisicao()
}
class Adaptee {
+requisicaoEspecifica()
}
Adapter ..|> Target
Adapter --> Adaptee
Cliente --> Target
- reaproveita código incompatível
- isola conversões em um ponto claro
- evita mudar cliente e serviço legado ao mesmo tempo
Contras
Seção intitulada “Contras”- pode esconder modelagem ruim interna
- adiciona mais uma camada de indireção
- pode crescer demais se acumular lógica extra
O Bridge separa abstração e implementação em hierarquias independentes para evitar explosão de subclasses.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- há duas dimensões independentes de variação
- a herança geraria muitas combinações
- abstração e implementação precisam evoluir separadamente
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class Abstraction {
-implementor: Implementor
+operacao()
}
class RefinedAbstraction
class Implementor {
<<interface>>
+operacaoImpl()
}
class ConcreteImplementorA
class ConcreteImplementorB
RefinedAbstraction --|> Abstraction
Abstraction --> Implementor
ConcreteImplementorA ..|> Implementor
ConcreteImplementorB ..|> Implementor
- evita explosão combinatória de subclasses
- permite trocar implementação em tempo de execução
- reduz acoplamento entre dois eixos de variação
Contras
Seção intitulada “Contras”- pode parecer abstrato demais no início
- adiciona mais camadas conceituais
- não vale a pena quando não há duas dimensões reais
Composite
Seção intitulada “Composite”O Composite permite tratar objetos simples e compostos de forma uniforme em estruturas de árvore.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- o domínio possui hierarquia parte-todo
- itens e grupos devem responder ao mesmo contrato
- operações recursivas são frequentes
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class Component {
<<interface>>
+operacao()
}
class Leaf {
+operacao()
}
class Composite {
-filhos: List~Component~
+adicionar(c: Component)
+remover(c: Component)
+operacao()
}
Leaf ..|> Component
Composite ..|> Component
Composite --> Component
- simplifica cliente em estruturas hierárquicas
- favorece recursão e composição uniforme
- facilita expansão da árvore
Contras
Seção intitulada “Contras”- interface comum pode ficar genérica demais
- folhas podem herdar operações pouco úteis
- complica cenários sem hierarquia real
Decorator
Seção intitulada “Decorator”O Decorator adiciona responsabilidades dinamicamente a um objeto por meio de envoltórios com a mesma interface.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- funcionalidades extras são opcionais e combináveis
- herança criaria muitas subclasses
- o comportamento deve ser montado em camadas
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class Component {
<<interface>>
+operacao()
}
class ConcreteComponent {
+operacao()
}
class Decorator {
-wrappee: Component
+operacao()
}
class ConcreteDecoratorA {
+operacao()
}
class ConcreteDecoratorB {
+operacao()
}
ConcreteComponent ..|> Component
Decorator ..|> Component
Decorator --> Component
ConcreteDecoratorA --|> Decorator
ConcreteDecoratorB --|> Decorator
- combina comportamentos sem explodir subclasses
- mantém o mesmo contrato para o cliente
- favorece extensão incremental
Contras
Seção intitulada “Contras”- cadeias longas dificultam depuração
- a ordem das camadas pode importar
- pode ficar mais difícil entender o objeto final
O Facade oferece uma interface simples para um subsistema mais complexo.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- o cliente só precisa de operações de alto nível
- há muitas classes técnicas ou etapas repetidas
- você quer reduzir acoplamento com um subsistema
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class Cliente
class Facade {
+operacaoDeAltoNivel()
}
class SubsistemaA
class SubsistemaB
class SubsistemaC
Cliente --> Facade
Facade --> SubsistemaA
Facade --> SubsistemaB
Facade --> SubsistemaC
- simplifica uso de subsistemas complexos
- reduz atrito e acoplamento para o cliente
- centraliza fluxos técnicos repetidos
Contras
Seção intitulada “Contras”- pode virar classe inchada
- pode esconder detalhes que às vezes importam
- não substitui bom desenho interno do subsistema
O Proxy controla o acesso a outro objeto por meio de um substituto com a mesma interface.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- o objeto real é caro, remoto ou sensível
- é necessário adicionar cache, log, lazy loading ou autorização
- o cliente deve continuar vendo o mesmo contrato
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class Subject {
<<interface>>
+requisicao()
}
class RealSubject {
+requisicao()
}
class Proxy {
-realSubject: RealSubject
+requisicao()
}
class Cliente
RealSubject ..|> Subject
Proxy ..|> Subject
Proxy --> RealSubject
Cliente --> Subject
- controla acesso sem alterar o cliente
- permite cache, proteção e inicialização tardia
- concentra políticas transversais em um ponto só
Contras
Seção intitulada “Contras”- adiciona indireção extra
- pode esconder custos reais de acesso remoto
- pode concentrar responsabilidades demais
Flyweight
Seção intitulada “Flyweight”O Flyweight compartilha estado intrínseco entre muitos objetos semelhantes para reduzir consumo de memória.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use quando:
- há muitos objetos parecidos em memória
- boa parte do estado se repete
- o ganho de compartilhamento compensa a complexidade extra
Diagrama de implementação
Seção intitulada “Diagrama de implementação”classDiagram
class Cliente
class Flyweight {
+operacao(estadoExtrinseco)
}
class ConcreteFlyweight
class FlyweightFactory {
-cache: Map
+obter(chave) Flyweight
}
class Contexto {
-estadoExtrinseco
}
ConcreteFlyweight ..|> Flyweight
FlyweightFactory --> Flyweight
Cliente --> FlyweightFactory
Cliente --> Contexto
Contexto --> Flyweight
- reduz uso de memória em coleções grandes
- evita duplicação de estado repetido
- favorece reuso de objetos compartilhados
Contras
Seção intitulada “Contras”- exige separação cuidadosa entre estados
- aumenta a complexidade do modelo
- pode ser otimização prematura se não houver volume real
Síntese dos Padrões Estruturais
Seção intitulada “Síntese dos Padrões Estruturais”Os padrões estruturais estudados mostram várias estratégias para lidar com composição e organização:
Adaptertraduz interfacesBridgesepara duas dimensões de variaçãoCompositeorganiza hierarquias parte-todoDecoratorempilha responsabilidadesFacadesimplifica acesso a subsistemasProxycontrola acesso a objetosFlyweightcompartilha estado repetido
Apesar de todos serem estruturais, cada um responde a um tipo diferente de acoplamento ou composição. Essa distinção é importante porque vários deles têm diagramas parecidos, mas intenções bem diferentes.
Tabela comparativa dos padrões estruturais
Seção intitulada “Tabela comparativa dos padrões estruturais”| Padrão | Ideia central | Aplicabilidade | Estrutura básica | Prós | Contras |
|---|---|---|---|---|---|
| Adapter | Traduzir interface incompatível | Integração com legado ou terceiros | Cliente, Target, Adapter, Adaptee | Reuso de componentes existentes | Camada extra e possível mascaramento de problema interno |
| Bridge | Separar abstração e implementação | Duas dimensões independentes de variação | Abstraction, Implementor e concretos | Evita explosão de subclasses | Mais abstrações para entender |
| Composite | Tratar folha e grupo igualmente | Árvores e composições parte-todo | Component, Leaf, Composite | Cliente uniforme em hierarquias | Interface pode ficar genérica demais |
| Decorator | Adicionar comportamento por camadas | Funcionalidades opcionais e combináveis | Component, Decorator, concretos | Combinação flexível sem herança excessiva | Cadeias longas dificultam leitura |
| Facade | Simplificar subsistema complexo | APIs extensas e fluxos técnicos repetidos | Cliente, Facade, subsistemas | Menor acoplamento para o cliente | Fachada pode inchar |
| Proxy | Controlar acesso mantendo a mesma interface | Cache, proteção, lazy loading, remoto | Subject, Proxy, RealSubject | Interceptação transparente ao cliente | Indireção e risco de esconder custos |
| Flyweight | Compartilhar estado comum | Muitos objetos semelhantes em memória | Flyweight, Factory, Contexto | Economia de memória | Separação de estados mais complexa |
Panorama Geral
Seção intitulada “Panorama Geral”Uma forma simples de comparar as categorias vistas até aqui é observar a pergunta dominante de cada uma:
| Categoria | Pergunta principal | Exemplos estudados |
|---|---|---|
| Criacional | Como criar objetos com menos acoplamento e mais controle? | Singleton, Builder, Factory Method, Abstract Factory, Prototype |
| Estrutural | Como organizar classes e objetos para colaborar melhor? | Adapter, Bridge, Composite, Decorator, Facade, Proxy, Flyweight |
| Comportamental | Como distribuir responsabilidades e interações? | Categoria apresentada, mas ainda não aprofundada em tópicos próprios |
Conclusão
Seção intitulada “Conclusão”Até aqui, a disciplina já mostrou que Design Patterns não devem ser entendidos como receita fixa, e sim como ferramentas de modelagem. Cada padrão resolve um tipo de tensão recorrente:
- excesso de dependência de classes concretas
- criação complexa ou repetitiva
- incompatibilidade entre interfaces
- necessidade de simplificar subsistemas
- controle de acesso ou compartilhamento eficiente de objetos
O principal ganho de estudar os padrões em conjunto é desenvolver repertório para decidir melhor. Em vez de decorar nomes, o objetivo é reconhecer sinais no problema:
- há variações na criação?
- há objetos demais com estado repetido?
- há camadas extras de comportamento?
- o cliente está conhecendo detalhes demais?
Quando essas perguntas começam a ficar claras, os padrões deixam de parecer catálogo teórico e passam a funcionar como apoio real no design de software.