𝗦𝘁𝗿𝘂𝗰𝘁𝘂𝗿𝗮𝗹 𝗗𝗲𝘀𝗶𝗴𝗻 𝗣𝗮𝘁𝘁𝗲𝗿𝗻𝘀: 𝗙𝗮𝗰𝗮𝗱𝗲, 𝗙𝗹𝘆𝘄𝗲𝗶𝗴𝗵𝘁, 𝗮𝗻𝗱 𝗣𝗿𝗼𝘅𝘆
Structural design patterns help you organize classes and objects.
Today we cover the final three patterns to complete your toolkit.
𝟭. 𝗧𝗵𝗲 𝗙𝗮𝗰𝗮𝗱𝗲 𝗣𝗮𝘁𝘁𝗲𝗿𝗻 Facade simplifies complex systems. It provides one simple interface to a group of messy classes.
Think of a movie theater. To watch a movie, you need to dim lights, start the projector, and open curtains. Instead of calling five different systems, you call one method: theater.watch_movie().
Use it when:
- You want to simplify a complex subsystem.
- You need a single entry point for a large API.
- You want to decouple clients from internal logic.
𝟮. 𝗧𝗵𝗲 𝗙𝗹𝘆𝘄𝗲𝗶𝗴𝗵𝘁 𝗣𝗮𝘁𝘁𝗲𝗿𝗻 Flyweight saves memory. It works when you have thousands of similar objects.
Instead of storing every detail in every object, you split the data. You keep shared, unchanging data (intrinsic state) in one place. You keep unique data (extrinsic state) separate.
Use it when:
- Memory usage is a real problem.
- You manage millions of similar objects, like characters in a text editor or particles in a game.
- You want to use object pooling to improve performance.
𝟯. 𝗧𝗵𝗲 𝗣𝗿𝗼𝘅𝘆 𝗣𝗮𝘁𝘁𝗲𝗿𝗻 Proxy acts as a stand-in for another object. It sits between the client and the real object to control access.
A proxy can:
- Lazy load: Load heavy images only when a user clicks them.
- Control access: Check if a user has permission to delete a database.
- Log activity: Track who uses a specific service.
- Cache results: Return saved data instead of running expensive logic.
Use it when:
- You need to delay expensive operations.
- You must protect a sensitive service.
- You want to add logging or security without changing the original class.
𝗦𝘂𝗺𝗺𝗮𝗿𝘆 𝗧𝗮𝗯𝗹𝗲
• 𝗔𝗱𝗮𝗽𝘁𝗲𝗿: Makes incompatible systems work together. • 𝗕𝗿𝗶𝗱𝗴𝗲: Decouples abstraction from implementation. • 𝗖𝗼𝗺𝗽𝗼𝘀𝗶𝘁𝗲: Builds tree structures. • 𝗗𝗲𝗰𝗼𝗿𝗮𝘁𝗼𝗿: Adds behavior without modifying classes. • 𝗙𝗮𝗰𝗮𝗱𝗲: Simplifies complex subsystems. • 𝗙𝗹𝘆𝘄𝗲𝗶𝗴𝗵𝘁: Shares data to save memory. • 𝗣𝗿𝗼𝘅𝘆: Controls access to objects.
The Golden Rule: Use these patterns to make code maintainable. Do not use them just to show off.
Next, we start the Behavioral Design Patterns series.
Mahdi Shamlou: Padrões de Projeto Estruturais 2026 - Facade, Flyweight, Proxy (Exemplos de Produção)
Os padrões de projeto estruturais lidam com a forma como classes e objetos são compostos para formar estruturas maiores. Eles ajudam a garantir que, quando o sistema cresce, a estrutura permaneça flexível e eficiente.
Neste artigo, exploraremos três padrões essenciais: Facade, Flyweight e Proxy, com exemplos práticos de produção.
1. Facade Pattern (Padrão Fachada)
O padrão Facade fornece uma interface simplificada para um conjunto complexo de classes, um subsistema ou um framework. Em vez de o cliente ter que interagir com várias classes e entender sua complexidade, ele interage apenas com a "fachada".
O Problema
Imagine um sistema de processamento de pedidos que envolve:
- Verificação de estoque.
- Processamento de pagamento.
- Cálculo de frete.
- Envio de e-mail de confirmação.
Sem o Facade, o cliente precisaria instanciar e coordenar cada uma dessas classes manualmente, lidando com as dependências de cada uma.
A Solução
Criamos uma classe OrderFacade que encapsula toda essa lógica. O cliente apenas chama orderFacade.placeOrder(order).
// Subsistema complexo
class Inventory {
checkStock(productId: string): boolean {
console.log(`Verificando estoque para o produto: ${productId}`);
return true;
}
}
class PaymentProcessor {
process(amount: number): boolean {
console.log(`Processando pagamento de R$ ${amount}`);
return true;
}
}
class ShippingService {
calculateShipping(productId: string): number {
console.log(`Calculando frete para o produto: ${productId}`);
return 15.0;
}
}
// Facade
class OrderFacade {
private inventory = new Inventory();
private payment = new PaymentProcessor();
private shipping = new ShippingService();
placeOrder(productId: string, amount: number): void {
if (this.inventory.checkStock(productId)) {
const shippingCost = this.shipping.calculateShipping(productId);
const total = amount + shippingCost;
if (this.payment.process(total)) {
console.log("Pedido realizado com sucesso!");
}
}
}
}
// Cliente
const orderFacade = new OrderFacade();
orderFacade.placeOrder("prod_123", 100.0);
Exemplo de Produção
Em arquiteturas de microserviços, um API Gateway atua frequentemente como um Facade, fornecendo um único ponto de entrada para diversos serviços internos complexos, simplificando a comunicação para o cliente (frontend ou mobile).
2. Flyweight Pattern (Padrão Peso-leve)
O padrão Flyweight é um padrão estrutural que visa minimizar o uso de memória ao compartilhar o máximo de dados possível com outros objetos semelhantes.
O Problema
Se você estiver desenvolvendo um jogo com milhares de árvores na tela, criar um objeto completo para cada árvore (contendo textura, modelo 3D, posição, cor, etc.) consumirá uma quantidade enorme de memória RAM, o que pode levar ao travamento do sistema.
A Solução
Dividimos o estado do objeto em dois:
- Estado Intrínseco: Dados compartilhados que não mudam entre as instâncias (ex: a textura da árvore).
- Estado Extrínseco: Dados que variam para cada instância (ex: a posição X e Y no mapa).
// Estado Intrínseco (Compartilhado)
class TreeType {
constructor(public name: string, public color: string, public texture: string) {}
}
// Flyweight Factory
class TreeFactory {
private treeTypes: Map<string, TreeType> = new Map();
getTreeType(name: string, color: string, texture: string): TreeType {
const key = `${name}_${color}_${texture}`;
if (!this.treeTypes.has(key)) {
this.treeTypes.set(key, new TreeType(name, color, texture));
}
return this.treeTypes.get(key)!;
}
}
// Objeto com Estado Extrínseco
class Tree {
constructor(private x: number, private y: number, private type: TreeType) {}
draw() {
console.log(`Desenhando uma ${this.type.name} (${this.type.color}) em [${this.x}, ${this.y}]`);
}
}
// Cliente
const factory = new TreeFactory();
const trees: Tree[] = [];
// Criando milhares de árvores, mas compartilhando o tipo
for (let i = 0; i < 1000; i++) {
const type = factory.getTreeType("Carvalho", "Verde", "textura_carvalho.png");
trees.push(new Tree(Math.random() * 100, Math.random() * 100, type));
}
trees[0].draw();
Exemplo de Produção
Sistemas de renderização de texto em editores de documentos ou navegadores utilizam o Flyweight para gerenciar caracteres. Em vez de cada letra armazenar todos os dados de fonte e glifo, elas compartilham esses dados e armazenam apenas sua posição e estilo individual.
3. Proxy Pattern (Padrão Proxy)
O padrão Proxy fornece um substituto ou um marcador de posição para outro objeto para controlar o acesso a ele. Um proxy pode interceptar chamadas para o objeto original, permitindo adicionar lógica adicional antes ou depois da chamada.
O Probleamento
Você tem um objeto que é muito "pesado" para carregar imediatamente (como uma imagem de alta resolução ou uma conexão de banco de dados) ou um objeto que exige verificações de segurança rigorosas antes de qualquer operação.
A Solução
O Proxy implementa a mesma interface que o objeto real. Ele decide quando carregar o objeto real ou quando permitir o acesso, agindo como um intermediário.
interface Image {
display(): void;
}
// Objeto Real (Pesado)
class RealImage implements Image {
constructor(private filename: string) {
this.loadFromDisk();
}
private loadFromDisk(): void {
console.log(`Carregando imagem pesada: ${this.filename}...`);
}
display(): void {
console.log(`Exibindo imagem: ${this.filename}`);
}
}
// Proxy
class ProxyImage implements Image {
private realImage: RealImage | null = null;
constructor(private filename: string) {}
display(): void {
if (this.realImage === null) {
this.realImage = new RealImage(this.filename);
}
this.realImage.display();
}
}
// Cliente
const image = new ProxyImage("foto_alta_resolucao.jpg");
// A imagem só é carregada do disco no momento do display() (Lazy Loading)
console.log("Proxy criado. Nada foi carregado ainda.");
image.display();
image.display();
Exemplo de Produção
Existem diferentes tipos de Proxy aplicados em produção:
- Virtual Proxy: Implementa o lazy loading de recursos pesados (como mostrado acima).
- Protection Proxy: Controla o acesso com base em permissões de usuário (ex: apenas administradores podem chamar certos métodos).
- Remote Proxy: Representa um objeto que reside em outro endereço de rede, ocultando a complexidade da comunicação de rede do cliente.