Padrões de Criação: Fábrica Simples, Método de Fábrica e Fábrica Abstrata

Os padrões de projeto de fábrica são essenciais para gerenciar a criação de objetos em sistemas orientados a objetos. Eles promovem o baixo acoplamento e aumentam a flexibilidade, delegando a responsabilidade de instanciação a classes específicas. Este artigo explora três variações comuns: Fábrica Simples, Método de Fábrica e Fábrica Abstrata.

Fábrica Simples (Simple Factory)

O padrão Fábrica Simples envolve uma classe central (a fábrica) responsável por criar diferentes tipos de objetos com base em um parâmetro de entrada. Embora não seja um padrão GoF formal, é amplamente utilizado e serve como base para padrões mais complexos.

Vantagens:

  • Desacoplamento de cliente-produto: O cliente interage apenas com a fábrica, sem precisar conhecer as classes concretas dos produtos. Isso reduz o acoplamento direto entre o cliente e os produtos específicos.
  • Encapsulamento da lógica de criação: Toda a lógica de criação (condicionais, configurações, etc.) é centralizada na fábrica, tornando o código mais organizado e fácil de manter.
  • Reutilização de código: A classe de fábrica pode ser reutilizada em diferentes partes de uma aplicação ou em diferentes plataformas.

Desvantagens:

  • Violação do Princípio Aberto/Fechado (OCP): Ao adicionar um novo tipo de produto, a classe de fábrica existente precisa ser modificada (geralmente adicionando um novo case em uma instrução switch). Isso viola o OCP, que prega que uma classe deve ser aberta para extensão, mas fechada para modificação.
  • Alto acoplamento com a fábrica: Embora desacople o cliente dos produtos concretos, ele introduz um forte acoplamento entre o cliente e a própria classe de fábrica.

Considere o exemplo de um sistema de notificações que pode enviar mensagens por e-mail, SMS ou push.

Exemplo de Código (Fábrica Simples):

using System;

// Produtos Abstratos e Concretos
public interface INotificationChannel
{
    void Deliver(string recipient, string message);
}

public class EmailNotification : INotificationChannel
{
    public void Deliver(string recipient, string message)
    {
        Console.WriteLine($"[Email] Para: {recipient} - Mensagem: {message}");
    }
}

public class SmsNotification : INotificationChannel
{
    public void Deliver(string recipient, string message)
    {
        Console.WriteLine($"[SMS] Para: {recipient} - Mensagem: {message}");
    }
}

public class PushNotification : INotificationChannel
{
    public void Deliver(string recipient, string message)
    {
        Console.WriteLine($"[Push] Para: {recipient} - Mensagem: {message}");
    }
}

// A Fábrica Simples
public static class NotificationFactory
{
    public static INotificationChannel CreateNotifier(string type)
    {
        return type.ToLower() switch
        {
            "email" => new EmailNotification(),
            "sms" => new SmsNotification(),
            "push" => new PushNotification(),
            _ => throw new ArgumentException("Tipo de notificação desconhecido.")
        };
    }
}

// Cliente
public class ClientApp
{
    public static void Run()
    {
        Console.WriteLine("--- Usando Fábrica Simples ---");
        INotificationChannel emailNotifier = NotificationFactory.CreateNotifier("email");
        emailNotifier.Deliver("john.doe@example.com", "Sua encomenda foi enviada!");

        INotificationChannel smsNotifier = NotificationFactory.CreateNotifier("sms");
        smsNotifier.Deliver("+123456789", "Seu código de verificação é 12345.");

        INotificationChannel pushNotifier = NotificationFactory.CreateNotifier("push");
        pushNotifier.Deliver("dispositivo_id_xyz", "Nova mensagem na sua caixa de entrada.");
    }
}

No exemplo acima, se precisarmos adicionar um novo tipo de notificação, como uma notificação por voz, teríamos que modificar a classe NotificationFactory.

Hierarquia de Produtos e Famílias de Produtos

Antes de mergulharmos nos próximos padrões, é crucial entender dois conceitos relacionados a produtos:

  • Hierarquia de Produtos (ou Nível de Produto): Refere-se a um único tipo de produto que possui diversas variações ou implementações. Por exemplo, um produto Logger pode ter implementações concretas como ConsoleLogger, FileLogger ou DatabaseLogger. O Método de Fábrica lida com uma única hierarquia de produtos.
  • Família de Produtos: Designa um conjunto de produtos relacionados que devem funcionar juntos. Por exemplo, um conjunto de interfaces de acesso a dados para diferentes entidades (IUserDao, IDepartmentDao, IProductDao) pode ter implementações específicas para SQL Server (SqlServerUserDao, SqlServerDepartmentDao) e Oracle (OracleUserDao, OracleDepartmentDao). A Fábrica Abstrata lida com a criação de famílias inteiras de produtos.

Método de Fábrica (Factory Method)

O padrão Método de Fábrica resolve a principal desvantagem da Fábrica Simples ao delegar a criação de objetos para subclasses. Em vez de uma única classe de fábrica com lógica condicional, cada tipo de produto concreto é criado por uma subclasse de fábrica específica.

Vantagens:

  • Conformidade com o OCP: Adicionar um novo tipo de produto não exige modificação nas classes existentes. Basta criar uma nova subclasse de fábrica e uma nova classe de produto.
  • Desacoplamento: O cliente depende apenas da interface do criador (fábrica abstrata) e da interface do produto abstrato, não das implementações concretas.
  • Flexibilidade: Permite que as subclasses decidam quais objetos instanciar, adaptando-se a diferentes contextos sem alterar o código principal.

Desvantagens:

  • Complexidade: Introduz mais classes (um criador abstrato, criadores concretos, um produto abstrato, produtos concretos) em comparação com a Fábrica Simples, aumentando a complexidade inicial do projeto.
  • Lida com uma única hierarquia: Cada fábrica concreta é projetada para criar um único tipo de produto em sua hierarquia. Se o objetivo é criar famílias de produtos relacionados, o padrão Fábrica Abstrata é mais adequado.

Vamos usar um exemplo de um sistema de log para ilustrar o Método de Fábrica, onde diferentes fábricas criam diferentes tipos de loggers.

Exemplo de Código (Método de Fábrica):

using System;

// Produto Abstrato
public interface IMessageLogger
{
    void LogMessage(string text);
}

// Produtos Concretos
public class ConsoleLogger : IMessageLogger
{
    public void LogMessage(string text)
    {
        Console.WriteLine($"[LOG CONSOLE] {text}");
    }
}

public class FileLogger : IMessageLogger
{
    public void LogMessage(string text)
    {
        Console.WriteLine($"[LOG ARQUIVO - simulado] {text}");
        // Em um cenário real, isso escreveria em um arquivo
    }
}

// Criador Abstrato (Fábrica Abstrata)
public abstract class LoggerFactoryBase
{
    // O método de fábrica
    public abstract IMessageLogger CreateLogger();

    // Uma operação que usa o produto criado pelo método de fábrica
    public void Operation(string message)
    {
        IMessageLogger logger = CreateLogger();
        logger.LogMessage(message);
    }
}

// Criadores Concretos (Fábricas Concretas)
public class ConsoleLoggerFactory : LoggerFactoryBase
{
    public override IMessageLogger CreateLogger()
    {
        return new ConsoleLogger();
    }
}

public class FileLoggerFactory : LoggerFactoryBase
{
    public override IMessageLogger CreateLogger()
    {
        return new FileLogger();
    }
}

// Cliente
public class FactoryMethodClient
{
    public static void Run()
    {
        Console.WriteLine("\n--- Usando Método de Fábrica ---");
        LoggerFactoryBase consoleCreator = new ConsoleLoggerFactory();
        consoleCreator.Operation("Esta é uma mensagem de log para o console.");

        LoggerFactoryBase fileCreator = new FileLoggerFactory();
        fileCreator.Operation("Esta mensagem deve ir para um arquivo.");
    }
}

Aqui, LoggerFactoryBase define o CreateLogger(), e as subclasses concretas (ConsoleLoggerFactory, FileLoggerFactory) implementam esse método para retornar suas respectivas instâncias de logger. O cliente interage com a LoggerFactoryBase, sem saber qual logger concreto está sendo usado.

Fábrica Abstrata (Abstract Factory)

O padrão Fábrica Abstrata fornece uma interface para criar famílias de objetos relacionados ou dependentes sem especificar suas classes concretas. Ele é útil quando um sistema deve ser independente de como seus produtos são criados, compostos e representados.

Vantagens:

  • Isolamento de classes concretas: O cliente é completamente isolado das classes de produto concretas. Ele interage apenas com as interfaces da fábrica e dos produtos.
  • Consistência de produtos: Garante que os produtos de uma família sejam compatíveis entre si, pois todos são criados pela mesma fábrica concreta.
  • Fácil troca de famílias de produtos: É fácil trocar uma família inteira de produtos por outra (por exemplo, mudar de um banco de dados SQL Server para Oracle) alterando apenas a fábrica concreta usada.

Desvantagens:

  • Complexidade aumentada: É o padrão de fábrica mais complexo, envolvendo muitas interfaces e classes para cada família de produtos e cada tipo de produto dentro da família.
  • Dificuldade em estender novas famílias de produtos: Se você precsiar adicionar um novo tipo de produto a uma família existente, a interface da fábrica abstrata e todas as suas implementações concretas precisarão ser modificadas.

Um exemplo clássico é a criação de objetos de acesso a dados (DAO) para diferentes bancos de dados, onde cada banco de dados (família) terá suas próprias implementações de DAOs para diferentes entidades (usuários, departamentos, produtos).

Exemplo de Código (Fábrica Abstrata):

using System;

// Família de Produtos: Interfaces de Acesso a Dados (DAOs)
public interface IUserDataAccess
{
    void AddUser(string username);
    void GetUser(int id);
}

public interface IDepartmentDataAccess
{
    void AddDepartment(string name);
    void ListDepartments();
}

// Implementações Concretas para SQL Server
public class SqlServerUserDataAccess : IUserDataAccess
{
    public void AddUser(string username) => Console.WriteLine($"[SQL Server DAO] Inserindo usuário: {username}");
    public void GetUser(int id) => Console.WriteLine($"[SQL Server DAO] Recuperando usuário com ID: {id}");
}

public class SqlServerDepartmentDataAccess : IDepartmentDataAccess
{
    public void AddDepartment(string name) => Console.WriteLine($"[SQL Server DAO] Adicionando departamento: {name}");
    public void ListDepartments() => Console.WriteLine("[SQL Server DAO] Listando todos os departamentos.");
}

// Implementações Concretas para Oracle
public class OracleUserDataAccess : IUserDataAccess
{
    public void AddUser(string username) => Console.WriteLine($"[Oracle DAO] Inserindo registro de usuário: {username}");
    public void GetUser(int id) => Console.WriteLine($"[Oracle DAO] Buscando registro de usuário com ID: {id}");
}

public class OracleDepartmentDataAccess : IDepartmentDataAccess
{
    public void AddDepartment(string name) => Console.WriteLine($"[Oracle DAO] Criando registro de departamento: {name}");
    public void ListDepartments() => Console.WriteLine("[Oracle DAO] Exibindo todos os registros de departamento.");
}

// Fábrica Abstrata
public interface IDatabaseFactory
{
    IUserDataAccess CreateUserDao();
    IDepartmentDataAccess CreateDepartmentDao();
}

// Fábricas Concretas para SQL Server e Oracle
public class SqlServerFactory : IDatabaseFactory
{
    public IUserDataAccess CreateUserDao() => new SqlServerUserDataAccess();
    public IDepartmentDataAccess CreateDepartmentDao() => new SqlServerDepartmentDataAccess();
}

public class OracleFactory : IDatabaseFactory
{
    public IUserDataAccess CreateUserDao() => new OracleUserDataAccess();
    public IDepartmentDataAccess CreateDepartmentDao() => new OracleDepartmentDataAccess();
}

// Cliente
public class AbstractFactoryClient
{
    public static void Run()
    {
        Console.WriteLine("\n--- Usando Fábrica Abstrata ---");
        Console.WriteLine("Conectando-se ao SQL Server:");
        IDatabaseFactory sqlDbFactory = new SqlServerFactory();
        IUserDataAccess sqlUserDao = sqlDbFactory.CreateUserDao();
        sqlUserDao.AddUser("João Silva");
        IDepartmentDataAccess sqlDeptDao = sqlDbFactory.CreateDepartmentDao();
        sqlDeptDao.ListDepartments();

        Console.WriteLine("\nConectando-se ao Oracle:");
        IDatabaseFactory oracleDbFactory = new OracleFactory();
        IUserDataAccess oracleUserDao = oracleDbFactory.CreateUserDao();
        oracleUserDao.AddUser("Maria Souza");
        IDepartmentDataAccess oracleDeptDao = oracleDbFactory.CreateDepartmentDao();
        oracleDeptDao.AddDepartment("Recursos Humanos");
    }
}

Neste exemplo, IDatabaseFactory é a fábrica abstrata que declara métodos para criar uma família de produtos (DAOs de usuário e departamento). SqlServerFactory e OracleFactory são as fábricas concretas que implementam esses métodos para retornar DAOs específicos do SQL Server e Oracle, respectivamente. O cliente pode facilmente alternar entre SQL Server e Oracle instanciando a fábrica concreta apropriada.

Em resumo, o Método de Fábrica é ideal para criar um único tipo de objeto (mas com múltiplas implementações), enquanto a Fábrica Abstrata é usada para criar famílias de objetos relacionados.

Tags: C# PadrõesDeProjeto FactoryPattern AbstractFactory FactoryMethod

Publicado em 10-12 19:24