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

 

netabstractfactoryfig01.JPG

 

  • 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.