Design Patterns: Factory Method

Flavio Ribeiro Lima
5 min readMar 6, 2022

--

O pattern Factory Method está na categoria creational patterns do GOF, é um padrão de criação.

Intenção

Definir uma interface para criar um objeto, mas deixar as subclasses decidirem que classe instanciar. O Factory Method permite adiar a instanciação para subclasses.

Por que usar Factory Method?

Quando precisar delegar a criação de objetos através de outras classes (subclasses) utilize esse padrão, com ele você adia a criação de objetos.

Com o Factory Method ao invés de chamar o operador new para criar os objetos, você chama um método de uma classe que devolve o objeto criado, esse método que utiliza o new para criar o objeto solicitado.

Quando utilizar?

  • Uma classe não pode antecipar a classe de objetos que deve criar.
  • Uma classe quer que suas subclasses especifiquem os objetos que criam.
  • Classes delegam responsabilidade para uma dentre várias subclasses auxiliares, e você quer localizar o conhecimento de qual subclasse auxiliar que é a delgada.

Participantes

  • Product: Define a interface de objetos que o método fábrica cria.
  • Concrete Product: Implementa a interface de Produtct.
  • Creator: Declara o método fábrica, o qual retorna um objeto do tipo Product. Creator pode também definir uma implementação por omissão do método factory que retorna por omissão um objeto ConcreteProduct. Pode chamar o método factory para criar um objeto Product.
  • ConcreteCreator: Redefine o método fábrica para retornar uma instância de um ConcreteProduct.

Funcionamento

Creator depende das suas subclasses para definir o método fábrica de maneira que retorne uma instância do ConcreteProduct apropriado.

Consequências

  • Elimina a necessidade de anexar classes específicas das aplicações no código. O código lida somente com a interface de Product, portanto, ele poe trabalhar com quaisquer classes ConcreteProduct definidas pelo usuário.
  • Fornece ganchos para subclasses: Criar objetos dentro de uma classe com um método fábrica é sempre mais flexível do que criar um obejto diretamente. Factory Method dá às subclasses um gancho para fornecer uma versão estendida de um objeto.

Implementação

Duas possibilidades são: Create ser uma classe abstrata e não fornecer uma implementação para o método fábrica que ela declara, ou quando Creator é uma classe concreta e fornece uma implementação, ou mesmo Creator ser uma classe abstrata e ainda sim fornecer uma implementação.

Método fábrica parametrizado: Uma possibilidade é o método fábrica criar múltiplos tipos de produtos. O método fábrica recebe um parâmetro que identifica o objeto a ser criado.

Estrutura

Abaixo exemplo do código padrão:

Considerações

A implementação do padrão não é difícil de absorver, o que você precisa entender é que os ConcreteCreators retornam um ConcreteProduct já instanciado, então é necessário apenas que o ConcreteProduct implemente a interface usada no retorno do método fábrica do ConcreteCreator.

Tome como exemplo o método FactoryMethod() da classe ConcreteCreatorA que tem como retorno IProduct que é a interface implementada por ConcreteProductA.

Na descrição do padrão diz que podemos receber um valor no parâmetro do método fábrica para decidir qual será o ConcreteProduct instanciado. Pensando nisso, vamos utilizar essa abordagem para resolver o problema que segue.

Exemplo

Digamos que você esteja construindo um CRM (Customer Relationship Management), nos sistemas de CRM existe o conceito de funil que determina a fase em que um possível cliente está no momento. Ao realizar um cadastro desse cliente na empresa, deve-se selecionar a etapa correta que pode ser Suspect ou Prospect, por exemplo.

Considerando esse regra de negócio, veja abaixo uma solução possível:

O exemplo acima é um tanto quanto prolixo e cheio de problemas, não se assuste se você encontrar código como esse implementado em algum projeto em produção.

Vamos refazer esse código usando o Factory Method. Abaixo veja o de-para para facilitar o entendimento:

  • IProduct => IFunil
  • ConcreteProduct => (Suspect, Prospect)
  • ICreator => IFunilFactory
  • ConcreteCreator => FunilFactory

Para deixar o exemplo um pouco mais elegante utilizei o enum Funil e a model FunilModel. Veja abaixo:

Veja que agora temos uma implementação melhor, com as responsabilidades separadas e a forma de obter o objeto correto para realizar o cadastro foi isolada no método ObterFunil() da classe FunilFactory (linha 40).

Para finalizar o exemplo, vamos adicionar o poder do Generics do C# e refatorar o método ObterFunil() da FunilFactory.

Primeiro a interface IFunilFactory:

Abaixo o método ObterFunil da FunilFactory, onde substitui o switch pela criação dinâmica de objeto, assim não preciso utilizar uma estrutura condicional.

E por fim a utilização do padrão:

Veja acima que agora passamos a classe (Suspect, Prospect) no parâmetro de tipo, ao invés de passar por parâmetro no método, e não utilizamos os tipos string, int, enum ou qualquer outro, ganhando mais expressividade e elegância.

Meu alerta aqui é quanto a performance que pode cair com o uso da criação do objeto de forma dinâmica (Activator.CreateInstance) e do cast para IFunil ao invés da versão original que utiliza um switch.

Outro ponto importante é em relação ao parâmetro do método Cadastrar nas classes Suspect e Prospect, note que utilizei a mesma classe (FunilModel), mas não necessariamente para cadastrar um Suspect ou Prospect você precisará dos mesmos parâmetros, e se for o caso, para o seu negócio, talvez faça mais sentido ter um único cadastro e classificar com um campo extra se se trata de um Suspect, Prospect ou outro. Outra ideia seria solicitar a classe model de forma genérica, mas isso irá adicionar uma complexidade no cadastro, que deve-se avaliar se vale a pena pagar por isso.

Para ver o projeto completo acesse: https://github.com/flaviorl-net/FactoryMethodCSharp

Conclusão

Sempre ao considerar o uso de um pattern, seja do GOF ou não, é importante verificar a aderência com as regras de negócio da empresa, escolher o pattern correto para o problema, e considerar os objetivos da empresa com o produto. Tendo como guia esses pontos, você poderá escolher o pattern correto para implementar um código com mais qualidade, com classes com responsabilidades bem segregadas e coesas.

Por hoje é pessoal, caso tenha chegado até aqui, espero que tenha gostado e que seja útil. Abraços e até próxima.

--

--

Flavio Ribeiro Lima

Desenvolvedor de Software. Entusiasta de boas práticas, padrões de desenvolvimento e arquitetura de sistemas.