Padrão de fábrica. Quando usar métodos de fábrica?


Respostas:


387

Gosto de pensar em pais de design em termos de minhas pessoas serem 'pessoas', e os padrões são as maneiras pelas quais as pessoas conversam entre si.

Então, para mim, o padrão de fábrica é como uma agência de contratação. Você tem alguém que precisará de um número variável de trabalhadores. Essa pessoa pode conhecer algumas informações de que precisa nas pessoas que contrata, mas é isso.

Portanto, quando precisam de um novo funcionário, ligam para a agência contratada e dizem o que precisam. Agora, para contratar alguém, você precisa conhecer muitas coisas - benefícios, verificação de elegibilidade etc. Mas a pessoa que contrata não precisa saber nada disso - a agência de contratação lida com tudo isso.

Da mesma forma, o uso de uma fábrica permite que o consumidor crie novos objetos sem precisar saber os detalhes de como eles são criados ou quais são suas dependências - eles só precisam fornecer as informações que realmente desejam.

public interface IThingFactory
{
    Thing GetThing(string theString);
}

public class ThingFactory : IThingFactory
{
    public Thing GetThing(string theString)
    {
        return new Thing(theString, firstDependency, secondDependency);
    }
}

Portanto, agora o consumidor da ThingFactory pode obter uma coisa, sem ter que saber sobre as dependências da coisa, exceto os dados da cadeia de caracteres que vem do consumidor.


17
Onde a implementação concreta de GetThing () recupera os valores de firstDependency e secondDependency?
Mikeyg36

88
Alguém poderia me dizer como isso responde à pergunta do OP? Isso apenas descreve o que é 'Factory Pattern' e, em seguida, adiciona um exemplo de 'Factory Method', que é apenas um dos três 'Factory Patterns'. Em outra palavra, não vejo comparação em lugar algum.
Forethinker

4
A pergunta do OP menciona claramente within an object instead of a Factory class. Eu acho que ele quis dizer o cenário em que você torna o ctor privado e usa um método estático para instanciar a classe (criar um objeto). Mas, para seguir este exemplo, é preciso instanciar a ThingFactoryclasse primeiro para obter Thingobjetos, o que torna isso Factory classefetivo.
precisa saber é

4
Desculpe, mas a explicação é péssima, porque um construtor também pode ser escrito de forma a ocultar as dependências. As principais informações básicas que você deseja separar as informações de criação de dependência do gerenciamento de dependência estão ausentes. Além disso, a pergunta era sobre a mesma classe, a resposta não tem nenhuma relação com isso.
Christian Hujer 8/08

8
OP perguntou quando . Kyoryu respondeu como . Embora o estilo da resposta seja louvável, no contexto desta pergunta, é apenas ruído.
8bjunkie 3/11

96

Os métodos de fábrica devem ser considerados como uma alternativa aos construtores - principalmente quando os construtores não são expressivos o suficiente, ou seja.

class Foo{
  public Foo(bool withBar);
}

não é tão expressivo quanto:

class Foo{
  public static Foo withBar();
  public static Foo withoutBar();
}

As classes de fábrica são úteis quando você precisa de um processo complicado para construir o objeto, quando a construção precisa de uma dependência que você não deseja para a classe real, quando você precisa construir objetos diferentes, etc.


2
Onde está a classe Factory aqui?
precisa

20
@KorayTugay: Não há classe de fábrica, apenas métodos de fábrica. A questão é sobre quando usar métodos de fábrica em vez de uma classe de fábrica. Mas os métodos de fábrica são mais uma alternativa para os construtores do que uma alternativa para as classes de fábrica. (Não sei por que a resposta principal é tão bem avaliada, apesar de falar apenas de classes de fábrica).
Rasmus Faber

5
Deve-se notar que os métodos estáticos de fábrica são completamente diferentes do padrão de projeto Gang of Four: Factory Method.
jaco0646

76

Uma situação em que pessoalmente encontro classes de fábrica separadas para fazer sentido é quando o objeto final que você está tentando criar se baseia em vários outros objetos. Por exemplo, no PHP: suponha que você tenha um Houseobjeto, que por sua vez tenha um Kitchene um LivingRoomobjeto, e o LivingRoomobjeto também tenha um TVobjeto dentro.

O método mais simples para conseguir isso é fazer com que cada objeto crie seus filhos em seu método de construção, mas se as propriedades estiverem relativamente aninhadas, quando você Housefalhar na criação, provavelmente passará algum tempo tentando isolar exatamente o que está falhando.

A alternativa é fazer o seguinte (injeção de dependência, se você gosta do termo sofisticado):

$TVObj = new TV($param1, $param2, $param3);
$LivingroomObj = new LivingRoom($TVObj, $param1, $param2);
$KitchenroomObj = new Kitchen($param1, $param2);
$HouseObj = new House($LivingroomObj, $KitchenroomObj);

Aqui, se o processo de criação de uma Housefalha falhar, há apenas um lugar para procurar, mas ter que usar essa parte toda vez que alguém quiser um novo Houseestá longe de ser conveniente. Entre nas fábricas:

class HouseFactory {
    public function create() {
        $TVObj = new TV($param1, $param2, $param3);
        $LivingroomObj = new LivingRoom($TVObj, $param1, $param2);
        $KitchenroomObj = new Kitchen($param1, $param2);
        $HouseObj = new House($LivingroomObj, $KitchenroomObj);

        return $HouseObj;
    }
}

$houseFactory = new HouseFactory();
$HouseObj = $houseFactory->create();

Graças à fábrica aqui, o processo de criação de um Houseé abstraído (pois você não precisa criar e configurar todas as dependências quando deseja apenas criar um House) e ao mesmo tempo centralizado, o que facilita a manutenção. Há outras razões pelas quais usar Fábricas separadas pode ser benéfico (por exemplo, testabilidade), mas acho esse caso de uso específico para ilustrar melhor como as classes Factory podem ser úteis.


1
Como alguém faria um teste de unidade sobre isso? Eu pensei que usar a palavra-chave "new" em uma classe era considerada uma prática ruim porque não pode ser testada em unidade. Ou uma fábrica deve ser uma exceção a essa regra?
AgmLauncher

1
@AgmLauncher tive a mesma pergunta, bem como quando eu comecei com o teste de unidade, confira: stackoverflow.com/questions/10128780/...
Mahn

1
Não entendi isso. Como exatamente os parâmetros para a criação de objetos diferentes são passados ​​para a HouseFactoryclasse?
precisa saber é

1
@ Mahn, você não acabaria tendo muitos parâmetros no final?
Pacerier

1
@Pacerier, é algo para você decidir como modelar, dependendo de suas necessidades, mas nem sempre é necessário passar todos os parâmetros para o createmétodo. Por exemplo, se você Housesempre tiver o mesmo tipo LivingRoom, pode fazer sentido ter seus parâmetros codificados na classe factory em vez de passar como argumentos. Ou você pode fornecer um typeargumento para o seu HouseFactory::createmétodo se tiver alguns tipos de se houver LivingRoomuma opção interna com os parâmetros codificados para cada tipo.
Mahn

19

É importante diferenciar claramente a idéia por trás do uso de fábrica ou método de fábrica. Ambos destinam-se a abordar diferentes tipos de problemas de criação de objetos, mutuamente exclusivos.

Vamos ser específicos sobre o "método de fábrica":

A primeira coisa é que, quando você estiver desenvolvendo bibliotecas ou APIs que, por sua vez, serão usadas para desenvolvimento adicional de aplicativos, o método factory será uma das melhores opções para o padrão de criação. Razão para trás; Sabemos que, quando criar um objeto com a (s) funcionalidade (s) necessária (s), o tipo de objeto permanecerá indeciso ou será decidido pelos parâmetros dinâmicos passados .

Agora, o ponto é que aproximadamente o mesmo pode ser alcançado usando o próprio padrão de fábrica, mas uma grande desvantagem será introduzida no sistema se o padrão de fábrica for usado para o problema destacado acima, é que sua lógica de criar objetos diferentes (objetos de subclasses) seja específico a alguma condição de negócios; portanto, no futuro, quando você precisar estender a funcionalidade da sua biblioteca para outras plataformas (em mais tecnicamente, você precisará adicionar mais subclasses de interface básica ou abstrata, para que o factory retorne esses objetos, além do existente) com base em alguns parâmetros dinâmicos), toda vez que você precisar alterar (estender) a lógica da classe de fábrica, que será uma operação dispendiosa e não boa da perspectiva do projeto. Por outro lado, se "método de fábrica"

interface Deliverable 
{
    /*********/
}

abstract class DefaultProducer 
{

    public void taskToBeDone() 
    {   
        Deliverable deliverable = factoryMethodPattern();
    }
    protected abstract Deliverable factoryMethodPattern();
}

class SpecificDeliverable implements Deliverable 
{
 /***SPECIFIC TASK CAN BE WRITTEN HERE***/
}

class SpecificProducer extends DefaultProducer 
{
    protected Deliverable factoryMethodPattern() 
    {
        return new SpecificDeliverable();
    }
}

public class MasterApplicationProgram 
{
    public static void main(String arg[]) 
    {
        DefaultProducer defaultProducer = new SpecificProducer();
        defaultProducer.taskToBeDone();
    }
}

15

Eles também são úteis quando você precisa de vários "construtores" com o mesmo tipo de parâmetro, mas com comportamento diferente.


15

É uma boa idéia usar métodos de fábrica dentro do objeto quando:

  1. A classe do objeto não sabe quais subclasses exatas ele deve criar
  2. A classe do objeto é projetada para que os objetos criados sejam especificados por subclasses
  3. A classe do objeto delega seus deveres a subclasses auxiliares e não sabe qual classe exata assumirá esses deveres

É uma boa idéia usar a classe abstrata de fábrica quando:

  1. Seu objeto não deve depender de como os objetos internos são criados e projetados
  2. O grupo de objetos vinculados deve ser usado juntos e você precisa atender a essa restrição
  3. O objeto deve ser configurado por uma das várias famílias possíveis de objetos vinculados que farão parte do seu objeto pai
  4. É necessário compartilhar objetos filhos mostrando apenas interfaces, mas não uma implementação

9

UML de

insira a descrição da imagem aqui

Produto: define uma interface dos objetos que o método Factory cria.

ConcreteProduct: Implementa a interface do produto

Criador: declara o método Factory

ConcreateCreator: implementa o método Factory para retornar uma instância de um ConcreteProduct

Declaração do problema: Crie um Factory of Games usando o Factory Methods, que define a interface do jogo.

Fragmento de código:

import java.util.HashMap;


/* Product interface as per UML diagram */
interface Game{
    /* createGame is a complex method, which executes a sequence of game steps */
    public void createGame();
}

/* ConcreteProduct implementation as per UML diagram */
class Chess implements Game{
    public Chess(){

    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Chess game");
        System.out.println("Opponents:2");
        System.out.println("Define 64 blocks");
        System.out.println("Place 16 pieces for White opponent");
        System.out.println("Place 16 pieces for Black opponent");
        System.out.println("Start Chess game");
        System.out.println("---------------------------------------");
    }
}
class Checkers implements Game{
    public Checkers(){

    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Checkers game");
        System.out.println("Opponents:2 or 3 or 4 or 6");
        System.out.println("For each opponent, place 10 coins");
        System.out.println("Start Checkers game");
        System.out.println("---------------------------------------");
    }
}
class Ludo implements Game{
    public Ludo(){

    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Ludo game");
        System.out.println("Opponents:2 or 3 or 4");
        System.out.println("For each opponent, place 4 coins");
        System.out.println("Create two dices with numbers from 1-6");
        System.out.println("Start Ludo game");
        System.out.println("---------------------------------------");
    }
}

/* Creator interface as per UML diagram */
interface IGameFactory {
    public Game getGame(String gameName);
}

/* ConcreteCreator implementation as per UML diagram */
class GameFactory implements IGameFactory {

     HashMap<String,Game> games = new HashMap<String,Game>();
    /*  
        Since Game Creation is complex process, we don't want to create game using new operator every time.
        Instead we create Game only once and store it in Factory. When client request a specific game, 
        Game object is returned from Factory instead of creating new Game on the fly, which is time consuming
    */

    public GameFactory(){

        games.put(Chess.class.getName(),new Chess());
        games.put(Checkers.class.getName(),new Checkers());
        games.put(Ludo.class.getName(),new Ludo());        
    }
    public Game getGame(String gameName){
        return games.get(gameName);
    }
}

public class NonStaticFactoryDemo{
    public static void main(String args[]){
        if ( args.length < 1){
            System.out.println("Usage: java FactoryDemo gameName");
            return;
        }

        GameFactory factory = new GameFactory();
        Game game = factory.getGame(args[0]);
        if ( game != null ){                    
            game.createGame();
            System.out.println("Game="+game.getClass().getName());
        }else{
            System.out.println(args[0]+  " Game does not exists in factory");
        }           
    }
}

resultado:

java NonStaticFactoryDemo Chess
---------------------------------------
Create Chess game
Opponents:2
Define 64 blocks
Place 16 pieces for White opponent
Place 16 pieces for Black opponent
Start Chess game
---------------------------------------
Game=Chess

Este exemplo mostra uma Factoryclasse implementando a FactoryMethod.

  1. Gameé a interface para todos os tipos de jogos. Ele define um método complexo:createGame()

  2. Chess, Ludo, Checkers são diferentes variantes de jogos, que fornecem implementação para createGame()

  3. public Game getGame(String gameName)está FactoryMethodna IGameFactoryaula

  4. GameFactorypré-cria diferentes tipos de jogos no construtor. Implementa o IGameFactorymétodo de fábrica.

  5. O nome do jogo é passado como argumento de linha de comando para NotStaticFactoryDemo

  6. getGamein GameFactoryaceita um nome de jogo e retorna o Gameobjeto correspondente .

Fábrica:

Cria objetos sem expor a lógica da instanciação ao cliente.

FactoryMethod

Defina uma interface para criar um objeto, mas deixe as subclasses decidirem qual classe instanciar. O método Factory permite que uma classe adie a instanciação para subclasses

Caso de uso:

Quando usar: Clientnão sabe quais classes concretas serão necessárias para criar em tempo de execução, mas apenas deseja obter uma classe que fará o trabalho.


obrigado por suas palestras, é conciso para mim .mas é inimaginável que "getArea () seja o Método de Fábrica na interface Shape", porque o método getArea NUNCA instancia qualquer classe, apenas faça um cálculo .reveja que " interface para criar um objeto, mas deixe as subclasses decidirem qual classe instanciar "por favor.
reco

getArea()não é um método de fábrica em tudo .
Cranio

1
Eu tenho uma opinião diferente - os especialistas validam e fazem anotações. 1. O cliente (ou o invocador) precisa de Objeto de Interesse ... portanto, não deve precisar chamar new GameFactory (); a classe Factory deve ter uma getInstance () estática 2.Também se for o caso, games.put (Chess.class.getName ( ), novo xadrez ()); sempre retornará a mesma referência de xadrez [se implementada como estática] - como lidar com esse cenário de maneira mais eficaz?
Arnab Dutta

Eu dei exemplo de fábrica não estática. Você pode implementá-lo com blocos e métodos estáticos, se desejar. Em relação às suas consultas: 1. O cliente ligará para a fábrica para obter o jogo. 2. Estou colocando o objeto uma vez e todos os Get retornam a mesma instância - a mesma referência do Xadrez é retornada para cada get
Ravindra babu

6

É realmente uma questão de gosto. As classes de fábrica podem ser abstraídas / com interface, conforme necessário, enquanto os métodos de fábrica são mais leves (e também tendem a ser testáveis, pois não têm um tipo definido, mas exigem um ponto de registro conhecido, semelhante a um serviço localizador, mas para localizar métodos de fábrica).


4

As classes de fábrica são úteis para quando o tipo de objeto que eles retornam possui um construtor privado, quando diferentes classes de fábrica definem propriedades diferentes no objeto de retorno ou quando um tipo de fábrica específico é associado ao seu tipo de concreto de retorno.

O WCF usa as classes ServiceHostFactory para recuperar objetos ServiceHost em diferentes situações. O ServiceHostFactory padrão é usado pelo IIS para recuperar instâncias do ServiceHost para arquivos .svc , mas um WebScriptServiceHostFactory é usado para serviços que retornam serializações para clientes JavaScript. O ADO.NET Data Services possui seu próprio DataServiceHostFactory e o ASP.NET possui seu ApplicationServicesHostFactory, pois seus serviços possuem construtores particulares.

Se você tiver apenas uma classe que está consumindo a fábrica, basta usar um método de fábrica nessa classe.


2

Considere um cenário em que você deve criar uma classe de Pedido e Cliente. Por questões de simplicidade e requisitos iniciais, você não sente necessidade de fábrica para a classe Order e preenche seu aplicativo com muitas instruções 'new Order ()'. As coisas estão funcionando bem.

Agora, surge um novo requisito: o objeto Order não pode ser instanciado sem a associação do Cliente (nova dependência). Agora você tem as seguintes considerações.

1- Você cria sobrecarga de construtor que funcionará apenas para novas implementações. (Não aceitável). 2- Você altera as assinaturas de Order () e altera cada invocação. (Não é uma boa prática e muita dor).

Em vez disso, se você criou uma fábrica para a classe de pedidos, é necessário alterar apenas uma linha de código e pronto. Sugiro classe Factory para quase todas as associações agregadas. Espero que ajude.


1

se você deseja criar um objeto diferente em termos de uso. É útil.

public class factoryMethodPattern {
      static String planName = "COMMERCIALPLAN";
      static int units = 3;
      public static void main(String args[]) {
          GetPlanFactory planFactory = new GetPlanFactory();
          Plan p = planFactory.getPlan(planName);
          System.out.print("Bill amount for " + planName + " of  " + units
                        + " units is: ");
          p.getRate();
          p.calculateBill(units);
      }
}

abstract class Plan {
      protected double rate;

      abstract void getRate();

      public void calculateBill(int units) {
            System.out.println(units * rate);
      }
}

class DomesticPlan extends Plan {
      // @override
      public void getRate() {
            rate = 3.50;
      }
}

class CommercialPlan extends Plan {
      // @override
      public void getRate() {
            rate = 7.50;
      }
}

class InstitutionalPlan extends Plan {
      // @override
      public void getRate() {
            rate = 5.50;
      }
}

class GetPlanFactory {

      // use getPlan method to get object of type Plan
      public Plan getPlan(String planType) {
            if (planType == null) {
                  return null;
            }
            if (planType.equalsIgnoreCase("DOMESTICPLAN")) {
                  return new DomesticPlan();
            } else if (planType.equalsIgnoreCase("COMMERCIALPLAN")) {
                  return new CommercialPlan();
            } else if (planType.equalsIgnoreCase("INSTITUTIONALPLAN")) {
                  return new InstitutionalPlan();
            }
            return null;
      }
}


1

Eu acho que vai depender do grau de acoplamento solto que você deseja trazer para o seu código.

O método de fábrica desacopla as coisas muito bem, mas a classe de fábrica não.

Em outras palavras, é mais fácil mudar as coisas se você usar o método de fábrica do que se usar uma fábrica simples (conhecida como classe de fábrica).

Veja este exemplo: https://connected2know.com/programming/java-factory-pattern/ . Agora, imagine que você quer trazer um novo animal. Na classe Factory, você precisa alterar a Factory, mas no método factory, não, você só precisa adicionar uma nova subclasse.


0

As classes de fábrica são mais pesadas, mas oferecem algumas vantagens. Nos casos em que você precisa construir seus objetos a partir de várias fontes de dados brutas, elas permitem que você encapsule apenas a lógica da construção (e talvez a agregação dos dados) em um único local. Lá, ele pode ser testado em abstrato sem se preocupar com a interface do objeto.

Eu achei esse um padrão útil, particularmente onde não consigo substituir e ORM inadequado e quero instanciar com eficiência muitos objetos de junções de tabela de DB ou procedimentos armazenados.


0

Eu comparo as fábricas ao conceito de bibliotecas. Por exemplo, você pode ter uma biblioteca para trabalhar com números e outra para trabalhar com formas. Você pode armazenar as funções dessas bibliotecas em diretórios nomeados logicamente como Numbersou Shapes. Estes são tipos genéricos que podem incluir números inteiros, flutuadores, dobules, compridos ou retângulos, círculos, triângulos, pentágonos no caso de formas.

O petter de fábrica usa polimorfismo, injeção de dependência e inversão de controle.

O objetivo declarado dos Padrões de Fábrica é: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Então, digamos que você esteja construindo um sistema operacional ou estrutura e todos os componentes discretos.

Aqui está um exemplo simples do conceito do Factory Pattern em PHP. Posso não estar 100% em tudo isso, mas pretende servir como um exemplo simples. Eu não sou um especialista.

class NumbersFactory {
    public static function makeNumber( $type, $number ) {
        $numObject = null;
        $number = null;

        switch( $type ) {
            case 'float':
                $numObject = new Float( $number );
                break;
            case 'integer':
                $numObject = new Integer( $number );
                break;
            case 'short':
                $numObject = new Short( $number );
                break;
            case 'double':
                $numObject = new Double( $number );
                break;
            case 'long':
                $numObject = new Long( $number );
                break;
            default:
                $numObject = new Integer( $number );
                break;
        }

        return $numObject;
    }
}

/* Numbers interface */
abstract class Number {
    protected $number;

    public function __construct( $number ) {
        $this->number = $number;
    }

    abstract public function add();
    abstract public function subtract();
    abstract public function multiply();
    abstract public function divide();
}
/* Float Implementation */
class Float extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Integer Implementation */
class Integer extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Short Implementation */
class Short extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Double Implementation */
class Double extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}
/* Long Implementation */
class Long extends Number {
    public function add() {
        // implementation goes here
    }

    public function subtract() {
        // implementation goes here
    }

    public function multiply() {
        // implementation goes here
    }

    public function divide() {
        // implementation goes here
    }
}

$number = NumbersFactory::makeNumber( 'float', 12.5 );

Entendo o que está acontecendo aqui, mas não entendo qual é o sentido disso. O que está NumbersFactory::makeNumber( 'float', 12.5 );me deixando apenas dizendo new Float(12.5);se eu sei que preciso de um Float? É isso que eu não entendo sobre as fábricas ... qual é o objetivo?
BadHorsie 19/10/2015

Permite escolher diferentes implementações e não vincula você a apenas uma. Uma interface é estabelecida e todas as implementações devem garantir e honrá-la.
Robert Rocha
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.