C# -  Padrão Abstract Factory


Neste artigo veremos o padrão de projeto Abstract Factory através de um exemplo prático.

O padrão de projeto Abstract Factory é um dos padrões de projeto entre os 23 padrões da Gang of Four (GoF) sendo um padrão de design criacional.

Este padrã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.

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. O uso deste padrão torna possível trocar implementações concretas sem alterar o código que estas usam, mesmo em tempo de execução.



Este padrão de projeto define uma interface para criar um objeto mas não especifica quais objetos as implementações individuais desta interface irão instanciar.  Ele deixa que as subclasses decidam qual classe instanciar permitindo também que uma classe adie a instanciação para as subclasses.

O diagrama UML deste padrão é fornecido abaixo.

A fábrica determina o tipo concreto do objeto a ser criado, e é nela que o objeto é realmente criado. No entanto, a fábrica só retorna um ponteiro abstrato para o objeto concreto criado. (wikipédia)

O código do cliente não tem conhecimento algum do tipo concreto e os objetos concretos são, de fato criados pela fábrica, mas o código do cliente acessa tais objetos só através da sua interface abstrata. (wikipédia)

Este padrão é uma extensão do padrão de factory method, que permite criar objetos sem se preocupar com a classe real do objeto que está sendo criado.

Quando usar este padrão ?

Use este padrão quando :

Para ilustrar a aplicação deste padrão de projetos vamos imaginar que precisamos criar objetos do tipo Pizza e objetos do tipo Bolo. Temos assim duas família de produtos que embora diferentes são feitos da mesma substância : a massa.

Vamos criar um projeto Console (.NET 5) usando o Visual Studio Community chamado ConsoleApp1.

Vamos definir que queremos criar Pizza de Mussarela e Calabreza e bolo de Chocolate e Laranja. Assim vamos criar agora 3 enumerações para definir os tipos com os quais vamos tratar : TipoMassa, TipoPizza e TipoBolo

 public enum TipoMassa
 {
    Pizza = 0,
    Bolo = 1,
 }
 public enum TipoPizza
 {
    Mussarela = 0,
    Calabreza = 1
 }
 public enum TipoBolo
 {
    Chocolate = 0,
    Laranja = 1
 }

Como temos duas famílias de objetos que desejamos criar, Pizza e Bolo, vamos criar uma classe abstrata MassaBase da qual Bolo e Pizza vão herdar:

using System.Collections;

namespace AbstractFactory1
{
    public abstract class MassaBase
    {
        public TipoMassa TipoMassa { get; set; }
        public string Nome { get; set; }

        public ArrayList Ingredientes = new ArrayList();

        public MassaBase(string nome, TipoMassa tipo)
        {
            Nome = nome;
            TipoMassa = tipo;
        }
    }
}

A seguir vamos definir as classes abstratas Bolo e Pizza que representam as famílias de objetos para a qual desejamos criar objetos e que herdam da classe MassaBase:

    //AbstractProductA
    public abstract class Bolo : MassaBase
    {
        public Bolo(string nome, TipoMassa tipo) : base(nome, tipo)
        {
        }
    }
      //AbstractProductB
    public abstract class Pizza : MassaBase
    {
        public Pizza(string nome, TipoMassa tipo) : base(nome, tipo)
        {
        }
    }

Agora podemos criar as classes concretas para PizzaCalabreza, PizzaMussarela, BoloChocolate e BoloLaranja que são os produtos concretos que iremos criar:

    //ProductB1
    public sealed class PizzaCalabreza : Pizza
    {
        public PizzaCalabreza() : base("Pizza Calabreza", TipoMassa.Pizza)
        {
            Ingredientes.Add("Calabreza em cubos e tomates em cubos");
        }
    }
    //ProductB2
    public sealed class PizzaMussarela : Pizza
    {
        public PizzaMussarela() : base("Pizza Mussarela", TipoMassa.Pizza)
        {
            Ingredientes.Add("Queijo mussarela gratinado e molho de tomate");
        }
    }
    //ProductA1
    public sealed class BoloChocolate : Bolo
    {
        public BoloChocolate() : base("Bolo de Chocolate", TipoMassa.Bolo)
        {
            Ingredientes.Add("Com cobertura de chocolate Nestlé");
        }       
    }
    //ProductA2
    public sealed class BoloLaranja : Bolo
    {
        public BoloLaranja() : base("Bolo de Laranja", TipoMassa.Bolo)
        {
            Ingredientes.Add("Com cobertura de calda de laranja");
        }
    }

Abaixo temos o diagrama de classe desta implementação:

Vamos agora criar a classe abstrata MassasAbstractFactory onde vamos definir um método que vai ser do tipo de uma interface IMassaFactoryMethod que com base no tipo de massa : bolo ou pizza, vai criar uma instância da fábrica que sabe criar pizzas e bolos.

Assim precisamos criar antes a interface IMassaFactoryMethod que é um factory method e que com base no tipo da massa cria a respectiva fábrica:

using System;

namespace AbstractFactory1.FactoryMethod
{
    public interface IMassaFactoryMethod
    {
        MassaBase CriaMassa(Enum massaFactoryType);
    }
}

A seguir temos o código da classe MassasAbstractFactory:

using AbstractFactory1.FactoryMethod;
using System;

namespace AbstractFactory1
{
    public abstract class MassasAbstractFactory
    {
      public static IMassaFactoryMethod CriaFabrica(TipoMassa tipoMassa)
      {
            switch (tipoMassa)
            {
                case TipoMassa.Pizza:
                {
                    return new PizzaFactory();
                }
                case TipoMassa.Bolo:
                {
                    return new BoloFactory();
                }
                default:
                  throw new ArgumentOutOfRangeException(nameof(tipoMassa),
                      tipoMassa, null);
            }
        }
    }
}

Agora vamos criar as classes concretas para PizzaFactory e BoloFactory que irão retornar instâncias das classes concretas dos bolos e das pizzas:

using AbstractFactory1.Enums;
using AbstractFactory1.FactoryMethod;
using System;

namespace AbstractFactory1
{
    public sealed class BoloFactory : IMassaFactoryMethod
    {
        public MassaBase CriaMassa(Enum massaFactoryType)
        {
            var tipoBolo = (TipoBolo)massaFactoryType;

            switch (tipoBolo)
            {
                case TipoBolo.Chocolate:
                {
                   return new BoloChocolate();
                }
                case TipoBolo.Laranja:
                {
                     return new BoloLaranja();
                }
                default:
                    throw new ArgumentOutOfRangeException("Tipo não implementado");
            }
        }
    }
}

using AbstractFactory1.Enums;
using AbstractFactory1.FactoryMethod;
using System;

namespace AbstractFactory1
{
    public sealed class PizzaFactory : IMassaFactoryMethod
    {
        public MassaBase CriaMassa(Enum massaFactoryType)
        {
            var tipoPizza = (TipoPizza)massaFactoryType;

            switch (tipoPizza)
            {
                case TipoPizza.Mussarela:
                {
                   return new PizzaMussarela();
                }
                case TipoPizza.Calabreza:
                {
                   return new PizzaCalabreza();
                }
                default:
                    throw new ArgumentOutOfRangeException("Tipo não implementado");
            }
        }
    }
}

Abaixo temos o diagrama de classes desta implementação:

Pronto, agora para poder criar objetos de cada tipo de massa e para isso vamos definir o código a seguir no método Main que representa o Cliente:

using AbstractFactory1.Enums;
using System;

namespace AbstractFactory1
{
    public class Program
    {
        private static void Main(string[] args)
        {
            //Obtém as fábricas
            var boloFactory = MassasAbstractFactory.CriaFabrica(TipoMassa.Bolo);
            var pizzaFactory = MassasAbstractFactory.CriaFabrica(TipoMassa.Pizza);

            //cria os objetos com base no tipo : bolo
            var bolo1 = boloFactory.CriaMassa(TipoBolo.Chocolate);
            var bolo2 = boloFactory.CriaMassa(TipoBolo.Laranja);

            //cria os objetos com base no tipo : pizza
            var pizza1 = pizzaFactory.CriaMassa(TipoPizza.Mussarela);
            var pizza2 = pizzaFactory.CriaMassa(TipoPizza.Calabreza);

            //exibe os detalhes
            ExibeDetalhes(bolo1);
            ExibeDetalhes(bolo2);
            ExibeDetalhes(pizza1);
            ExibeDetalhes(pizza2);

            Console.ReadLine();
        }

        private static void ExibeDetalhes(MassaBase massaBase)
        {
            Console.WriteLine($"Tipo : {massaBase.TipoMassa}");
            Console.WriteLine(massaBase.Nome);
            Console.WriteLine(massaBase.Ingredientes[0].ToString());
            Console.WriteLine("\n");
        }
    }
}

Resultado:

O padrão Abstract Factory  apresenta as seguintes vantagens/desvantagens:

  • Ele isola as classes concretas e 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, ele isola os clientes das classes de implementação. Os clientes manipulam as instâncias através das suas interfaces abstratas.
     
  • Torna fácil trocar 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. 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.
     
  • 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.
  • Pegue o código do projeto aqui :   AbstractFactory1.zip

    "Se o SENHOR não edificar a casa, em vão trabalham os que a edificam; se o SENHOR não guardar a cidade, em vão vigia a sentinela."
    Salmos 127:1

    Referências:


    José Carlos Macoratti