Desenvolvimento

6 dez, 2018

Arquitetura e desenvolvimento de software – Parte 02: Abstract Factory

Publicidade

E ai, pessoal! Tudo certo?

Na primeira parte do artigo fizemos uma reflexão do papel do arquiteto de software no time e como arquitetura de software deve ser algo de interesse de todos para que não exista alguém centralizado de onde tudo depende.

Vimos também sobre os padrões definidos pelo GoF (Gang of Four). Aliás, recomendo fortemente que leiam o livro.

Nestas próximas partes, vamos analisar detalhadamente cada um dos 23 padrões, suas propostas, quando aplicar, quando não aplicar, vantagens e desvantagens de cada um. Começaremos com o primeiro da lista que apresentei, o Abstract Factory.

Abstract Factory

O padrão Abstract Factory é o primeiro Pattern descrito no livro Design Patterns do GoF. Ela faz parte da categoria de Patterns Criacionais, cujo objetivo é a instanciação de objetos. Essa categoria é importante, pois ela sustenta o princípio mais importante do livro: “programe para interfaces e não para implementações”.

Atualmente, os Patterns Criacionais estão em desuso, sendo substituídos pelos frameworks de Injeção de Dependência, que fazem exatamente isso: instanciam para você as classes das quais você é dependente.

De toda forma, conhecer os patterns, sua motivação e entender suas consequências é um bom exercício de design de software.

O problema que este padrão tenta resolver é da instanciação na existência de uma família de produtos. O exemplo dado no livro é o seguinte: você tem elementos visuais (produtos), como Window, ScrollBar, Menu e etc.

Esses elementos visuais têm diferentes implementações para cada família de implementação gráfica, como o Windows, MAC, e X, do Linux.

Nesse caso, a solução consiste em duas partes:

  • 1. Crie interfaces padrões para os diferentes produtos dessa família (Window, ScrollBar e Menu). E todo o seu sistema vai trabalhar apenas com essas interfaces que você definiu.
  • 2. Defina uma Abstract Factory, que tem os métodos de instanciação para cada uma dessas interfaces padrões definidas acima. No caso, os métodos para o exemplo seriam: CreateWindow, CreateScrollBar, CreateMenu. Sempre que o sistema precisar de instâncias dos produtos ele irá conseguir as mesmas através dessa Abstract Factory.

Mas de onde virá essa Factory? Em algum lugar, provavelmente na inicialização do seu sistema, você define qual Abstract Factory você vai fornecer ao sistema, e repassa essa Factory a todos os objetos que dependem dela. Veja o esquema:

Diagrama UML com exemplo de uso de uma Abstract Factory

A figura acima, retirada do livro o Client, representa os demais módulos do sistema, utiliza as classes abstratas dos produtos (AbstractProductA e AbstractProductB) e a AbstractFactory, assim como foi explicado.

É importante agora analisar os benefícios e malefícios desse pattern. O ponto principal é que o pattern deixa seu sistema independente das diferentes famílias. Ou seja, garante o baixo acoplamento citado anteriormente.

Outro ponto positivo é que este pattern permite adicionar, remover ou modificar rapidamente qual família de produtos deseja-se usar. Isso pode até ser feito em runtime, se usado com um pouco de Reflection para instanciar uma fábrica através de parâmetros (ou com um array referenciando as possíveis fábricas).

Já um ponto negativo desse pattern, é que a adição ou remoção de um produto da família exige a modificação da AbstractFactory, o que causa um grande overhead, pois deve-se modificar todas as implementações da Factory e o cliente que usa a AbstractFactory.

Na verdade, usando Reflection pode até criar um único método “Create” que recebe como parâmetro alguma indicação de qual produto ele deve criar (uma string com o nome da classe, por exemplo). Dessa forma, você poderia criar um novo produto sem muitos problemas – basta passar o parâmetro certo.

Mas isso funciona como uma balança: se você ganha flexibilidade na criação de novos produtos, você perde com a necessidade de uma interface única para todos esses produtos, já que eles serão retornados pelo mesmo método “Create”. E o seu Client precisará fazer uma conversão para o produto específico.

Ficha resumo

Vamos à “ficha” de definição deste pattern.

  • Nome: Abstract Factory
  • Objetivo/intenção: Permite a criação de famílias de objetos relacionados ou dependentes por meio de uma única interface e sem que a classe concreta seja especificada. Também é conhecido como Kit
  • Motivação: O objetivo em empregar o padrão é isolar a criação de objetos de seu uso e criar famílias de objetos relacionados sem ter que depender de suas classes concretas. Isso permite novos tipos derivados de serem introduzidos sem qualquer alteração ao código que usa a classe base
  • Aplicabilidade: Cenários onde uma família de produtos ou classes precisa ser instanciado, sem dependência de suas classes concretas, como no exemplo do livro, onde você tem elementos visuais (produtos), como Window, ScrollBar, Menu e etc. Esses elementos visuais têm diferentes implementações para cada família de implementação gráfica, como o Microsoft Windows, o MAC, e o X, do Linux
  • Estrutura: Abaixo, temos um exemplo de estrutura onde o cliente apenas conhece a AbstractFactory e as classes abstratas dos produtos
Diagrama UML com exemplo de uso de uma Abstract Factory
  • Consequências: temos a vantagem de utilizarmos apenas classes abstratas ou interfaces, o que diminui muito o acoplamento entre as classes do sistema, assim como podemos adicionar, modificar ou remover produtos da família de forma rápida. Mas, temos como ponto negativo que a adição ou remoção de famílias exige a modificação da AbstractFactory, o que causa um grande overhead, pois deve-se modificar todas as implementações da Factory e o cliente que usa a AbstractFactory
  • Implementações: conforme o exemplo, temos abaixo o código em Java que demonstra a implementação do padrão para a produção de produtos de famílias diferentes
public class FabricaAbstrataExemplo {

    public static void main(String[] args) {

        FabricaAbstrata fabrica1 = new FabricaConcreta1();
        Cliente cliente1 = new Cliente(fabrica1);
        cliente1.executar();

        FabricaAbstrata fabrica2 = new FabricaConcreta2();
        Cliente cliente2 = new Cliente(fabrica2);
        cliente2.executar();
    }
}

class Cliente {
    private ProdutoAbstratoA produtoA;
    private ProdutoAbstratoB produtoB;

    Cliente(FabricaAbstrata fabrica) {
        produtoA = fabrica.createProdutoA();
        produtoB = fabrica.createProdutoB();
    }

    void executar() {
        produtoB.interagir(produtoA);
    }
}

interface FabricaAbstrata {
    ProdutoAbstratoA createProdutoA();
    ProdutoAbstratoB createProdutoB();
}

interface ProdutoAbstratoA {

}

interface ProdutoAbstratoB {
    void interagir(ProdutoAbstratoA a);
}

class FabricaConcreta1 implements FabricaAbstrata {

    @Override
    public ProdutoAbstratoA createProdutoA() {
        return new ProdutoA1();
    }
    @Override
    public ProdutoAbstratoB createProdutoB() {
        return new ProdutoB1();
    }
}

class FabricaConcreta2 implements FabricaAbstrata {

    @Override
    public ProdutoAbstratoA createProdutoA() {
        return new ProdutoA2();
    }
    @Override
    public ProdutoAbstratoB createProdutoB() {
        return new ProdutoB2();
    }
}

class ProdutoA1 implements ProdutoAbstratoA {

}

class ProdutoB1 implements ProdutoAbstratoB {

    @Override
    public void interagir(ProdutoAbstratoA a) {
        System.out.println(this.getClass().getNome() + " interage com " + a.getClass().getNome());
    }

}

class ProdutoA2 implements ProdutoAbstratoA {

}

class ProdutoB2 implements ProdutoAbstratoB {

    @Override
    public void interagir(ProdutoAbstratoA a) {
        System.out.println(this.getClass().getNome() + " interage com " + a.getClass().getNome());
    }

}
  • Usos conhecidos: o padrão Abstract Factory pode ser utilizado na implementação de um toolkit que disponibilize controles que funcionem em diferentes interfaces gráficas, tal como Motif, GTK+ (GNOME) ou Qt (KDE). Essas GUIs possuem diferentes padrões de controles visuais e, para facilitar a construção de aplicações que interajam facilmente com diferentes interfaces gráficas, é interessante que se definam interfaces comuns para acesso aos controles, independentemente da GUI utilizada. Este problema pode ser resolvido por meio de uma classe abstrata que declara uma interface genérica para criação dos controles visuais e de uma classe abstrata para criação de cada tipo de controle. O comportamento específico, de cada um dos padrões tecnológicos contemplados, é implementado por meio de uma classe concreta. O aplicativo, ou “cliente”, interage com o toolkit por meio das classes abstratas sem ter conhecimento da implementação das classes concretas
  • Padrões relacionados: Factory Method, Prototype e Singleton

Concluindo

Este foi o padrão Abstract Factory. O mesmo facilita bastante quando trabalhamos com aplicações que devem funcionar com ambientes diferentes, como interfaces gráficas, sistemas de banco de dados diferentes, mas onde temos bases comuns, apenas com comportamentos específicos definidos para cada “ambiente”.

Na próxima parte desta série vamos abordar o padrão Factory Method.

Deixem as dúvidas e feedbacks sobre a série nos comentários e até a próxima parte!