Design Patterns
Criacionais
Abstract Factory

Padrão de Projeto: Abstract Factory

Propósito

O Abstract Factory é um padrão de projeto criacional que permite que você produza famílias de objetos relacionados sem ter que especificar suas classes concretas.

Problema

Imagine que você está criando um simulador de loja de mobílias. Seu código consiste de classes que representam:

  • Uma família de produtos relacionados, como: Cadeira + Sofá + MesaDeCentro.
  • Várias variantes dessa família. Por exemplo, produtos Cadeira + Sofá + MesaDeCentro estão disponíveis nas variantes: Moderno, Vitoriano, ArtDeco.

Você precisa de um jeito de criar objetos de mobília individuais para que eles combinem com outros objetos da mesma família. Além disso, você não quer mudar o código existente quando adiciona novos produtos ou famílias de produtos ao programa.

Solução

O padrão Abstract Factory sugere declarar explicitamente interfaces para cada produto distinto da família de produtos (ex: cadeira, sofá ou mesa de centro). Então, você pode fazer todas as variantes dos produtos seguirem essas interfaces.

O próximo passo é declarar a fábrica abstrata—uma interface com uma lista de métodos de criação para todos os produtos que fazem parte da família de produtos (por exemplo, criarCadeira, criarSofá e criarMesaDeCentro). Esses métodos devem retornar tipos abstratos de produtos representados pelas interfaces que extraímos previamente.

Para cada variante de uma família de produtos, criamos uma classe fábrica separada baseada na interface FábricaAbstrata. Uma fábrica é uma classe que retorna produtos de um tipo em particular.

Estrutura

  1. Produtos Abstratos: Declaram interfaces para um conjunto de produtos distintos mas relacionados que fazem parte de uma família de produtos.
  2. Produtos Concretos: Várias implementações de produtos abstratos, agrupados por variantes.
  3. Fábrica Abstrata: Declara um conjunto de métodos para criação de cada um dos produtos abstratos.
  4. Fábricas Concretas: Implementam métodos de criação fábrica abstratos. Cada fábrica concreta corresponde a uma variante específica de produtos e cria apenas aquelas variantes de produto.

Exemplo em PHP

Interfaces de Produtos

interface Cadeira {
    public function hasLegs();
    public function sitOn();
}
 
interface Sofa {
    public function lieOn();
}
 
interface MesaDeCentro {
    public function placeItems();
}

Produtos Concretos

class CadeiraVitoriana implements Cadeira {
    public function hasLegs() {
        return true;
    }
 
    public function sitOn() {
        echo "Sentando em uma cadeira vitoriana.\n";
    }
}
 
class SofaVitoriano implements Sofa {
    public function lieOn() {
        echo "Deitando em um sofá vitoriano.\n";
    }
}
 
class MesaDeCentroVitoriana implements MesaDeCentro {
    public function placeItems() {
        echo "Colocando itens em uma mesa de centro vitoriana.\n";
    }
}
 
class CadeiraModerna implements Cadeira {
    public function hasLegs() {
        return false;
    }
 
    public function sitOn() {
        echo "Sentando em uma cadeira moderna.\n";
    }
}
 
class SofaModerno implements Sofa {
    public function lieOn() {
        echo "Deitando em um sofá moderno.\n";
    }
}
 
class MesaDeCentroModerna implements MesaDeCentro {
    public function placeItems() {
        echo "Colocando itens em uma mesa de centro moderna.\n";
    }
}

Interface da Fábrica Abstrata

interface FabricaDeMobilia {
    public function criarCadeira(): Cadeira;
    public function criarSofa(): Sofa;
    public function criarMesaDeCentro(): MesaDeCentro;
}

Fábricas Concretas

class FabricaVitoriana implements FabricaDeMobilia {
    public function criarCadeira(): Cadeira {
        return new CadeiraVitoriana();
    }
 
    public function criarSofa(): Sofa {
        return new SofaVitoriano();
    }
 
    public function criarMesaDeCentro(): MesaDeCentro {
        return new MesaDeCentroVitoriana();
    }
}
 
class FabricaModerna implements FabricaDeMobilia {
    public function criarCadeira(): Cadeira {
        return new CadeiraModerna();
    }
 
    public function criarSofa(): Sofa {
        return new SofaModerno();
    }
 
    public function criarMesaDeCentro(): MesaDeCentro {
        return new MesaDeCentroModerna();
    }
}

Código Cliente

function clientCode(FabricaDeMobilia $fabrica) {
    $cadeira = $fabrica->criarCadeira();
    $sofa = $fabrica->criarSofa();
    $mesa = $fabrica->criarMesaDeCentro();
 
    $cadeira->sitOn();
    $sofa->lieOn();
    $mesa->placeItems();
}
 
echo "Cliente: Testando código cliente com a fábrica vitoriana:\n";
clientCode(new FabricaVitoriana());
 
echo "\nCliente: Testando código cliente com a fábrica moderna:\n";
clientCode(new FabricaModerna());

Exemplo em Node.js

Interfaces de Produtos

class Cadeira {
    hasLegs() {
        throw new Error("Método 'hasLegs()' deve ser implementado.");
    }
 
    sitOn() {
        throw new Error("Método 'sitOn()' deve ser implementado.");
    }
}
 
class Sofa {
    lieOn() {
        throw new Error("Método 'lieOn()' deve ser implementado.");
    }
}
 
class MesaDeCentro {
    placeItems() {
        throw new Error("Método 'placeItems()' deve ser implementado.");
    }
}

Produtos Concretos

class CadeiraVitoriana extends Cadeira {
    hasLegs() {
        return true;
    }
 
    sitOn() {
        console.log("Sentando em uma cadeira vitoriana.");
    }
}
 
class SofaVitoriano extends Sofa {
    lieOn() {
        console.log("Deitando em um sofá vitoriano.");
    }
}
 
class MesaDeCentroVitoriana extends MesaDeCentro {
    placeItems() {
        console.log("Colocando itens em uma mesa de centro vitoriana.");
    }
}
 
class CadeiraModerna extends Cadeira {
    hasLegs() {
        return false;
    }
 
    sitOn() {
        console.log("Sentando em uma cadeira moderna.");
    }
}
 
class SofaModerno extends Sofa {
    lieOn() {
        console.log("Deitando em um sofá moderno.");
    }
}
 
class MesaDeCentroModerna extends MesaDeCentro {
    placeItems() {
        console.log("Colocando itens em uma mesa de centro moderna.");
    }
}

Interface da Fábrica Abstrata

class FabricaDeMobilia {
    criarCadeira() {
        throw new Error("Método 'criarCadeira()' deve ser implementado.");
    }
 
    criarSofa() {
        throw new Error("Método 'criarSofa()' deve ser implementado.");
    }
 
    criarMesaDeCentro() {
        throw new Error("Método 'criarMesaDeCentro()' deve ser implementado.");
    }
}

Fábricas Concretas

class FabricaVitoriana extends FabricaDeMobilia {
    criarCadeira() {
        return new CadeiraVitoriana();
    }
 
    criarSofa() {
        return new SofaVitoriano();
    }
 
    criarMesaDeCentro() {
        return new MesaDeCentroVitoriana();
    }
}
 
class FabricaModerna extends FabricaDeMobilia {
    criarCadeira() {
        return new CadeiraModerna();
    }
 
    criarSofa() {
        return new SofaModerno();
    }
 
    criarMesaDeCentro() {
        return new MesaDeCentroModerna();
    }
}

Código Cliente

function clientCode(fabrica) {
    const cadeira = fabrica.criarCadeira();
    const sofa = fabrica.criarSofa();
    const mesa = fabrica.criarMesaDeCentro();
 
    cadeira.sitOn();
    sofa.lieOn();
    mesa.placeItems();
}
 
console.log("Cliente: Testando código cliente com a fábrica vitoriana:");
clientCode(new FabricaVitoriana());
 
console.log("\nCliente: Testando código cliente com a fábrica moderna:");
clientCode(new FabricaModerna());

Aplicabilidade

  • Use o Abstract Factory quando seu código precisa trabalhar com diversas famílias de produtos relacionados, mas você não quer depender de classes concretas daqueles produtos.
  • Considere implementar o Abstract Factory quando você tem uma classe com um conjunto de métodos fábrica que desfoquem sua responsabilidade principal.

Prós e Contras

Prós

  • Você pode ter certeza que os produtos que você obtém de uma fábrica são compatíveis entre si.
  • Você evita um vínculo forte entre produtos concretos e o código cliente.
  • Princípio de responsabilidade única: facilita a manutenção do código.
  • Princípio aberto/fechado: permite introduzir novas variantes de produtos sem quebrar o código cliente existente.

Contras

  • O código pode tornar-se mais complicado do que deveria ser, uma vez que muitas novas interfaces e classes são introduzidas junto com o padrão.

Relações com outros padrões

  • Muitos projetos começam usando o Factory Method e evoluem para o Abstract Factory, Prototype, ou Builder.
  • O Builder foca em construir objetos complexos passo a passo. O Abstract Factory se especializa em criar famílias de objetos relacionados.
  • Classes Abstract Factory são quase sempre baseadas em um conjunto de métodos fábrica.
  • O Abstract Factory pode servir como uma alternativa para o Facade quando você precisa apenas esconder do código cliente a forma com que são criados os objetos do subsistema.
  • Você pode usar o Abstract Factory junto com o Bridge. Esse pareamento é útil quando algumas abstrações definidas pelo Bridge só podem trabalhar com implementações específicas.

Este guia fornece uma visão abrangente do padrão Abstract Factory, ilustrando sua aplicação com exemplos em PHP e Node.js. Ele é essencial para criar sistemas flexíveis e extensíveis, permitindo a criação de famílias de objetos de maneira desacoplada e seguindo os princípios de design orientado a objetos.