Design Patterns Catalog

Simplificando o que não é complicado

Abstract Factory

O objetivo do design pattern Abstract Factory é permitir instanciar objetos de uma mesma família em um ponto central, sem precisar que o restante da aplicação precise conhecer as classes concretas que são instanciadas.

Considere um framework para construção de jogos que seja capaz de orquestrar objetos como o Player (o jogador), Enemy (os inimigos apresentados na tela), Map (o mapa), Phase (fases) dentre outros. Um objeto Phase, por exemplo, tem um Map associado, além de objetos do tipo Enemy que são instanciados no decorrer do jogo.

Esse framework pode ser utilizado tanto para vários tipos de jogos, como os de plataforma (onde um personagem corre por um cenário) ou os shoot’em’up, jogos onde uma nave ou um avião percorre um mapa atirando em tudo o que vê. . Porém, as classes concretas que implementam as interfaces Player, Enemy, Map, etc são diferentes para cada tipo de jogo. Precisamos de uma maneira de instanciar os objetos que compoem o jogo sem amarrá-los ao framework (essa é a parte que diz que a aplicação não precisa conhecer as classes concretas) e de forma que seja fácil trocar toda uma família de objetos, ou seja, poder instanciar mapas, inimigos, etc compatíveis entre si, seja para um jogo de plataforma ou seja para um jogo de tiro.

A abordagem do design pattern Abstract Factory consiste em definir uma interface, digamos, ElementsFactory, utilizada pelo framework responsável por instanciar os elementos. Essa interface define os métodos createMap, createPlayer, createPhase e createEnemy, responsáveis por criar objetos que implementem as interfaces Map, Player, Phase, e Enemy respectivamente. Cada tipo de jogo, então, contará com a sua implementação de ElementsFactory que tem a tarefa de criar as classes concretas que compoem cada jogo.

Então para o nosso jogo de plataforma temos a implementação da ElementsFactory na forma da classe PlatformElementsFactory, que retorna instâncias de PlatformPlayer (implementação da interface Player), PlatformEnemy (implementação da interface Enemy) e daí por diante.

O framework conta com a PlatformElementsFactory para instanciar objetos que ele só conhece por interfaces abstratas, o que permite criar novos jogos somente alterando a implementação da factory concreta e implementando os objetos por ela gerados. E ainda conseguimos garantir que os objetos utilizados pelo framework são compatíveis entre si (não faria sentido manipular uma nave espacial no meio de um jogo de plataforma!).

O Abstract Factory permite:

  • Isolar classes concretas, já que a responsabilidade de criá-las é de um objeto Factory. O sistema cliente lida com as classes instanciadas utilizando as respectivas interfaces abstratas, fazendo com que as classes concretas não apareçam no resto do código.
  • Alterar toda uma família de produtos (classes) utilizadas em um sistema facilmente, uma vez que a Factory concreta aparece no código do sistema somente no ponto onde é instanciada. E a troca da implementação da Factory pode (e provavelmente irá) resultar na troca das classes concretas criadas por ela.
  • Garantir a consistência entre produtos que foram planejados para trabalhar em conjunto. Uma vez que o processo de instanciar produtos de uma família passam obrigatoriamente pela mesma Factory, é mais fácil garantir que sempre serão criados produtos compatíveis entre si.

Um ponto a ser observado no uso desse design pattern é que alterar a interface Factory abstrata para adicionar um novo tipo de produto nos obriga a implementar a criação desse novo produto em todas as suas implementações (em todas as factories concretas). Na prática isso não é tão grave pois, se você precisa alterar a Factory para suportar um novo produto, é provável que as demais factories concretas também tenham que suportar esse novo tipo de produto de forma a atender às exigências do sistema que as utilizam.