O Padrão Fábrica na prática
Quando você precisa criar objetos sem acoplar a lógica de instanciação ao código que vai usá-los, o padrão fábrica é a resposta mais direta. Ele existe nos livros como padrão de criação e, na maior parte das vezes, funciona exatamente como prometido. O problema real começa quando a fábrica cresce até virar um monstro.
assinale o padrão que utiliza uma fabrica de objetos
O padrão que utiliza uma fábrica de objetos é o Factory Method. Uma classe base define um método abstrato ou virtual para criar objetos, e as subclasses decidem qual concreto instanciar. Existe também a Abstract Factory, que é uma fábrica de fábricas, mas quando o enunciado fala apenas em "fábrica de objetos", o que se espera é o Factory Method mesmo. Vou explicar a diferença porque isso causa confusão constante. No Factory Method, você tem algo assim em linhas gerais:
Uma classe que declara um método factory. As subclasses sobrescrevem esse método e retornam instâncias diferentes de uma interface comum. O cliente chama o método da classe e não sabe qual classe concreta está sendo criada. Isso quebra o acoplamento direto com os construtores concretos. Já a Abstract Factory agrupa fábricas relacionadas em uma única interface. Você tem um produto A e um produto B que precisam ser criados juntos, e a fábrica garante que eles sejam compatíveis entre si. É mais complexo, mais pesado, e nem sempre necessário.
Um exemplo simples. Digamos que você tenha um sistema de relatórios. Você quer gerar relatórios em PDF e em Excel. Com Factory Method, você cria uma classe abstract ReportFactory com um método createReport(). PDFReportFactory sobrescreve e retorna um objeto PDF. ExcelReportFactory retorna um objeto Excel. O código cliente chama reportFactory.createReport() e só usa a interface Report, sem saber se é PDF ou Excel. Isso parece simples porque é simples. O padrão em si é basicamente um switch disfarçado de polimorfismo. E às vezes ele é exatamente o que você precisa.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Agora vou contar o problema que eu enfrentei. Trabalhei em um sistema de notificações onde tínhamos notificações por email, SMS e push notification. A fábrica criava os provedores corretos baseando-se no canal configurado no banco de dados. Funcionou bem por seis meses. Até que precisei adicionar um quarto canal e, junto com isso, variáveis de configuração específicas por canal. A fábrica cresceu para 400 linhas com ifs encadeados. O teste unitário ficou insuportável. O código que deveria ser flexível virou um trapaceiro difícil de modificar. A solução que eu usei foi abandonar a fábrica única e adotar uma combinação de registro de provedores com injeção de dependência. Em vez de uma fábrica central decidindo tudo, cada provedor se registrava em um contêiner. O contêiner resolvia as dependências automaticamente. Isso cortou o tempo de adição de um novo canal de duas horas para cerca de quinze minutos. Não é uma bala de prata, mas funcionou.
Aqui vão algumas insights que ninguém conta nos tutoriais. Primeiro, o Factory Method não elimina a necessidade de ifs. Ele apenas os move para outro lugar. Se você ainda depende de condições para decidir qual fábrica instanciar, o problema migrou, não sumiu. Segundo, o padrão funciona melhor quando há uma hierarquia natural de subclasses. Se você não consegue definir claramente quais subclasses existem e quais behaviors elas compartilham, force o padrão e o código vai sofrer. Outro ponto que os iniciantes ignoram. Fábricas criam objetos, mas elas não devem conter lógica de negócio. Se sua fábrica está validando dados, consultando bancos ou tomando decisões baseadas em regras de domínio, ela está fazendo coisas demais. Separe a responsabilidade. A fábrica só deve criar e retornar instâncias.
Sobre limites e falhas. O padrão se degrada rapidamente quando o número de produtos cresce acima de cinco ou seis. Cada novo produto exige uma nova subclass de fábrica. A árvore de herança fica profunda e frágil. Em cenários assim, o registro dinâmico de provedores ou até mesmo reflection com convenções de nomenclatura pode ser mais eficiente. Eu já vi times usarem uma factory registry onde os provedores são descobertos automaticamente por anotações ou convenções de assembly. Elimina subclasses novas para cada produto. A desvantagem é que a rastreabilidade diminui. Quando algo quebra, você gasta mais tempo entendendo do que com o padrão tradicional. Outro cenário onde o Factory Method falha. Quando você precisa de objetos com estado complexo no momento da criação. Se a criação depende de múltiplos parâmetros que variam em tempo de execução, a fábrica precisa aceitar todos eles. O construtor da fábrica vira uma monstruosidade. Nesse caso, o Builder Pattern costuma ser mais adequado. Ele separa a construção da representação do objeto.
Se você está estudando para uma prova ou entrevista e a pergunta pede para identificar o padrão que utiliza uma fábrica de objetos, a resposta esperada é Factory Method. Mas se quiser entender como isso se traduz para código real, pense em três coisas. Um método abstrato de criação na classe base. Subclasses que sobrescrevem esse método. E um cliente que só conhece a interface do produto. Na prática, a maioria dos projetos que eu vi usa uma versão simplificada do padrão. Uma classe estática com um método que decide qual tipo criar baseada em um parâmetro. Isso não é errado. É apenas menos elegante que a versão com herança. E em muitos casos, menos elegante é mais fácil de manter.
Se você quer um exemplo concreto de código. Uma interface IMessageFormatter com métodos Format() e GetSupportedFormats(). Uma classe base abstract MessageFormatterFactory com um método abstrato CreateFormatter(string type). Subclasses CsvFormatterFactory e JsonFormatterFactory que implementam o create e retornam instâncias de seus formatos. O cliente chama factory.CreateFormatter("json") e recebe um formatters que implementa a interface. Sem depender de new diretamente. Isso é tudo que existe sobre o padrão. Ele é útil, previsível e limitado. Conheça esses limites antes de aplicá-lo cegamente.