Padrões de projeto em .NET: Abstract Factory
Também conhecido como Kit, este padrão fornece uma interface para criação de famílias de objetos relacionados ou dependentes sem especificar suas classes concretas.
Para esclarecer a motivação para utilização deste padrão, considere a construção de um carro. Ao escolher um carro de uma determinada marca e modelo, um comprador sabe que está levando para casa, obrigatoriamente, determinadas peças. Porém, ele sabe, também, que itens como cor, airbag e direção hidráulica são itens opcionais, ou seja, podem estar presentes no carro a ser comprado ou não, influenciando em seu preço. Para ser portátil entre as várias opções, uma aplicação não deve codificar rigidamente seus componentes para um determinado padrão. Instanciando classes específicas de personalização pela aplicação toda torna difícil mudar tal personalização no futuro.
Podemos resolver esse problema definindo uma classe abstrata CarroFactory que declara uma interface para criação de cada tipo básico de carro. Existe, também, uma classe abstrata para cada tipo de carro, e subclasses concretas implementam os componentes para personalização. A interface de CarroFactory tem uma operação que retorna um novo objeto Carro para cada classe abstrata de Carro. Os clientes chamam estas operações para obter instâncias de Carro, mas não têm conhecimento das classes concretas que estão usando. Desta forma, os clientes ficam independentes do padrão de personalização usado no momento.
Existe uma subclasse concreta de CarroFactory para cada tipo de personalização. Cada subclasse implementa as operações para criar o carro apropriado para aquele tipo de personalização. Por exemplo, a operação CriarMotor aplicada à PoloCarroFactory instancia e adiciona um componente motor ao carro, de acordo com os padrões Volkswagen para o modelo Polo, enquanto que a mesma operação aplicada à StiloCarroFactory instancia um componente motor ao carro de acordo com os padrões Fiat para o modelo Stilo. Os clientes criam componentes exclusivamente através da interface de CarroFactory e não têm conhecimento das classes que implementam os componentes para um padrão específico. Em outras palavras, os clientes têm somente que se comprometer com uma interface definida por uma classe abstrata, não uma determinada classe concreta. Uma CarroFactory também implementa e garante as dependências entre as classes concretas de componentes. Um motor para o modelo Volkswagen Polo deveria ser usado somente com um carro modelo Volkswagen Polo, e essa restrição é garantida automaticamente como conseqüência de usar uma PoloCarroFactory
Quando usar Abstract Factory?
Use o padrão Abstract Factory quando:
- Um sistema deve ser independente de como seus produtos são criados, compostos ou representados;
- Um sistema deve ser configurado como um produto de uma família de múltiplos produtos;
- Uma família de objetos for projetada para ser usada em conjunto, e você necessita garantir esta restrição;
- Você quer fornecer uma biblioteca de classes e quer revelar somente suas interfaces, não suas implementações.
Estrutura
- AbstractFactory (CarroFactory): Declara uma interface para a criação de objetos abstratos.
- ConcreteFactory (PoloCarroFactory, StiloCarroFactory): Implementa as operações que criam objetos concretos.
- AbstractProduct (Motor): Declara uma interface para um tipo de objeto.
- ConcreteProduct (MotorPolo, MotorStilo): Define um objeto a ser criado pela fábrica concreta correspondente e implementa a interface de AbstractProduct.
- Client: Utiliza somente as interfaces declaradas pelas classes AbstractFactory e AbstractProduct.
Vantagens e desvantagens
O padrão Abstract Factory tem as seguintes vantagens e desvantagens:
- Ele isola as classes concretas. O padrão Abstract Factory ajuda a controlar as classes de objetos criadas por uma aplicação. Uma vez que a fábrica encapsula a responsabilidade e o processo de criar objetos, isola os clientes das classes de implementação. Os clientes manipulam as instâncias através das suas interfaces abstratas. Nomes de classes ficam isolados na implementação da fábrica concreta.
- Ele torna fácil a troca de famílias de produtos. A classe de uma fábrica concreta aparece apenas uma vez numa aplicação, isto é, quando é instanciada. Isso torna fácil mudar a fábrica concreta que uma aplicação usa. Ela pode usar diferentes configurações de objetos simplesmente trocando a fábrica concreta. Uma vez que a fábrica abstrata cria uma família completa de objetos, toda família de objetos muda de uma só vez. No nosso exemplo de construção de carros, podemos mudar de componentes do Polo para componentes do Stilo simplesmente trocando as correspondentes fábricas e recriando o carro.
- Ela promove a harmonia entre produtos. Quando objetos numa família são projetados para trabalharem juntos, é importante que uma aplicação use objetos de somente uma família de cada vez. Abstract Factory torna fácil assegurar isso.
- É difícil suportar novos tipos de produtos. Estender fábricas abstratas para produzir novos tipos de produtos não é fácil. Isso se deve ao fato de que a interface de Abstract Factory fixa o conjunto de produtos que podem ser criados. Suportar novos tipos de produtos exige estender a interface da fábrica, o que envolve mudar a classe AbstractFactory e todas as suas subclasses.
Exemplo de código
Aplicaremos o padrão AbstractFactory para criar os carros que discutimos no início do artigo.
A classe CarFactory pode criar modelos de carros e seus respectivos componentes. Ela define motor, pneu, porta, entre outros. Esta classe pode ser usada por uma aplicação que lê as especificações de um determinado modelo de carro em um arquivo XML, por exemplo, e constrói o respectivo carro, ou pode ser usada por uma aplicação que constrói carros aleatoriamente. As aplicações que constroem os carros recebem CarFactory como parâmetro, de maneira que o desenvolvedor possa especificar as classes de motor, pneu e porta a serem construídas.
Public Interface ICarroFactory
Function CriarCarro() As ICarro
Function CriarMotor() As IMotor
Function CriarPneu() As IPneu
Function CriarPorta() As IPorta
End Interface
Vale frisar que a função MontarCarro cria um carro básico, consistindo em 2 portas, 4 pneus e 1 motor. É claro que estes itens não são suficientes para que um carro funcione. Porém, a intenção, aqui, é exemplificar a utilização do padrão AbstractFactory.
Public Function MontarCarro(ByVal factory As ICarroFactory) As ICarro
Dim ListaPneus As List(Of IPneu)
Dim ListaPortas As List(Of IPorta)
Dim OCarro As ICarro = factory.CriarCarro
Dim OMotor As IMotor = factory.CriarMotor
Dim OPneu1 As IPneu = factory.CriarPneu
ListaPneus.Add(OPneu1)
Dim OPneu2 As IPneu = factory.CriarPneu
ListaPneus.Add(OPneu2)
Dim OPneu3 As IPneu = factory.CriarPneu
ListaPneus.Add(OPneu3)
Dim OPneu4 As IPneu = factory.CriarPneu
ListaPneus.Add(OPneu4)
Dim OPorta1 As IPorta = factory.CriarPorta
ListaPortas.Add(OPorta1)
Dim OPorta2 As IPorta = factory.CriarPorta
ListaPortas.Add(OPorta2)
OCarro = AdicionarMotor(OMotor)
For Each OPneu As IPneu In ListaPneus
OCarro = AdicionarPneu(OPneu)
Next
For Each OPorta As IPneu In ListaPortas
OCarro = AdicionarPorta(OPorta)
Next
Return OCarro
End Function
Podemos criar PoloCarroFactory, uma fábrica para carros do modelo Volkswagen Polo.
Public Class PoloCarroFactory
Implements ICarroFactory
Public Function CriarCarro() As ICarro Implements ICarroFactory.CriarCarro
Return New CarroPolo
End Function
Public Function CriarMotor() As IMotor Implements ICarroFactory.CriarMotor
Return New MotorPolo
End Function
Public Function CriarPneu() As IPneu Implements ICarroFactory.CriarPneu
Return New PneuPolo
End Function
Public Function CriarPorta() As IPorta Implements ICarroFactory.CriarPorta
Return New PortaPolo
End Function
End Class
E podemos, também, criar StiloCarroFactory, para carros do modelo Fiat Stilo.
Public Class StiloCarroFactory
Implements ICarroFactory
Public Function CriarCarro() As ICarro Implements ICarroFactory.CriarCarro
Return New CarroStilo
End Function
Public Function CriarMotor() As IMotor Implements ICarroFactory.CriarMotor
Return New MotorStilo
End Function
Public Function CriarPneu() As IPneu Implements ICarroFactory.CriarPneu
Return New PneuStilo
End Function
Public Function CriarPorta() As IPorta Implements ICarroFactory.CriarPorta
Return New PortaStilo
End Function
End Class
Repare que, para ambos os modelos, as classes são implementadas pela mesma interface, assim como seus métodos retornam objetos das mesmas interfaces. Isso garante que todos os carros, independente do modelo, terão 1 motor, 4 pneus e 2 portas. Porém, quesitos específicos de cada modelo, como design da porta, marca dos pneus e especificações de motor são informados de forma independente por cada fabricante, Fiat ou Volkswagen, de acordo com as implementações que seus desenvolvedores farão sobre cada interface.
No próximo artigo estaremos discutindo mais um padrão de criação: o Builder.