Patrón de fábrica ¿Cuándo usar los métodos de fábrica?

Respuestas:

386

Me gusta pensar en los patrones de diseño en términos de que mis clases sean 'personas', y los patrones son las formas en que las personas se comunican entre sí.

Entonces, para mí, el patrón de fábrica es como una agencia de contratación. Tienes a alguien que necesitará un número variable de trabajadores. Es posible que esta persona conozca la información que necesita sobre las personas que contrata, pero eso es todo.

Entonces, cuando necesitan un nuevo empleado, llaman a la agencia de contratación y les dicen lo que necesitan. Ahora, para contratar a alguien, necesita saber muchas cosas: beneficios, verificación de elegibilidad, etc. Pero la persona que contrata no necesita saber nada de esto: la agencia de contratación se encarga de todo eso.

Del mismo modo, el uso de una Fábrica permite al consumidor crear nuevos objetos sin tener que conocer los detalles de cómo se crean o cuáles son sus dependencias, solo tienen que proporcionar la información que realmente desean.

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

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

Entonces, ahora el consumidor de ThingFactory puede obtener una Cosa, sin tener que conocer las dependencias de la Cosa, a excepción de los datos de cadena que provienen del consumidor.

Kyoryu
fuente
17
¿Dónde recupera la implementación concreta de GetThing () los valores de firstDependency y secondDependency?
Mikeyg36
88
¿Alguien podría decirme cómo responde esto a la pregunta del OP? Esto simplemente describe qué es 'Patrón de fábrica' y luego agrega un ejemplo de 'Método de fábrica', que es solo uno de los tres 'Patrones de fábrica'. En otras palabras, no veo comparación en ninguna parte.
Forethinker
44
La pregunta de OP menciona claramente within an object instead of a Factory class. Creo que se refería al escenario en el que hace que el ctor sea privado y use un método estático para crear instancias de la clase (crear un objeto). Pero para seguir este ejemplo, primero se debe crear una instancia de la ThingFactoryclase para obtener Thingobjetos, lo que hace que esto sea Factory classefectivo.
atiyar
44
Lo sentimos, pero la explicación es una porquería, porque un constructor también se puede escribir para ocultar las dependencias. Falta la información de fondo clave de la que desea separar la información de creación de dependencia de la gestión de dependencia. Además, la pregunta era sobre dentro de la misma clase, la respuesta no está relacionada con eso.
Christian Hujer
8
OP preguntó cuándo . Kyoryu respondió cómo . Aunque el estilo de la respuesta es encomiable, en el contexto de esta pregunta, es solo ruido.
8bitjunkie el
96

Los métodos de fábrica deben considerarse como una alternativa a los constructores, principalmente cuando los constructores no son lo suficientemente expresivos, es decir.

class Foo{
  public Foo(bool withBar);
}

no es tan expresivo como:

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

Las clases de fábrica son útiles cuando necesita un proceso complicado para construir el objeto, cuando la construcción necesita una dependencia que no desea para la clase real, cuando necesita construir diferentes objetos, etc.

Rasmus Faber
fuente
2
¿Dónde está la clase Factory aquí?
Koray Tugay
20
@KorayTugay: No hay clase de fábrica, solo métodos de fábrica. La pregunta es sobre cuándo usar métodos de fábrica en lugar de una clase de fábrica. Pero los métodos de fábrica son más una alternativa a los constructores que una alternativa a las clases de fábrica. (No sé por qué la respuesta principal tiene una calificación tan alta a pesar de hablar solo de clases de fábrica).
Rasmus Faber
55
Cabe señalar que los métodos de fábrica estáticos son completamente diferentes del patrón de diseño Gang of Four: Factory Method.
jaco0646
76

Una situación en la que personalmente encuentro clases separadas de Factory para que tenga sentido es cuando el objeto final que está tratando de crear se basa en varios otros objetos. Por ejemplo, en PHP: suponga que tiene un Houseobjeto, que a su vez tiene un Kitcheny un LivingRoomobjeto, y el LivingRoomobjeto también tiene un TVobjeto dentro.

El método más simple para lograr esto es hacer que cada objeto cree sus hijos en su método de construcción, pero si las propiedades están relativamente anidadas, cuando Housefalla la creación, probablemente pasará algún tiempo tratando de aislar exactamente lo que está fallando.

La alternativa es hacer lo siguiente (inyección de dependencia, si le gusta el término elegante):

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

Aquí, si el proceso de creación de una Housefalla, solo hay un lugar para buscar, pero tener que usar esta porción cada vez que se quiera una nueva Houseestá lejos de ser conveniente. Ingrese a las 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();

Gracias a la fábrica aquí, el proceso de creación de una Housese abstrae (en el sentido de que no es necesario crear y configurar cada dependencia cuando solo se desea crear una House) y, al mismo tiempo, se centraliza, lo que facilita su mantenimiento. Hay otras razones por las que el uso de Fábricas separadas puede ser beneficioso (por ejemplo, la capacidad de prueba), pero encuentro este caso de uso específico para ilustrar mejor cómo las clases de Fábrica pueden ser útiles.

Mahn
fuente
1
Sin embargo, ¿cómo haría alguien una prueba de unidad en esto? Pensé que usar la palabra clave "nuevo" en una clase se consideraba una mala práctica porque no se puede probar por unidad. ¿O se supone que una fábrica es una excepción a esa regla?
AgmLauncher
1
@AgmLauncher También tuve la misma pregunta cuando comencé con las pruebas unitarias, consulte: stackoverflow.com/questions/10128780/…
Mahn
1
No entendí esto. ¿Cómo se pasan exactamente los parámetros para crear diferentes objetos a la HouseFactoryclase?
atiyar
1
@Mahn, ¿no terminarías teniendo muchos params al final?
Pacerier
1
@Pacerier, eso es algo para que usted decida cómo modelar, según sus necesidades, pero no siempre tiene que pasar todos los parámetros al createmétodo. Por ejemplo, si Houseva a tener siempre el mismo tipo de LivingRoomcosas, entonces puede tener sentido tener sus parámetros codificados en la clase de fábrica en lugar de pasarlos como argumentos. O es posible que desee proporcionar un typeargumento a su HouseFactory::createmétodo si tiene algunos tipos de LivingRoomsy tiene un interruptor dentro con los parámetros codificados para cada tipo.
Mahn
19

Es importante diferenciar claramente la idea detrás del uso de fábrica o método de fábrica. Ambos están destinados a abordar diferentes tipos de problemas de creación de objetos mutuamente excluyentes.

Seamos específicos sobre el "método de fábrica":

Lo primero es que, cuando está desarrollando una biblioteca o API que, a su vez, se utilizarán para un mayor desarrollo de aplicaciones, el método de fábrica es una de las mejores selecciones para el patrón de creación. Razón detrás; Sabemos cuándo crear un objeto de las funciones requeridas pero el tipo de objeto permanecerá indeciso o se decidirá si se pasan los parámetros dinámicos .

Ahora el punto es que se puede lograr aproximadamente lo mismo usando el patrón de fábrica en sí mismo, pero se introducirá un gran inconveniente en el sistema si el patrón de fábrica se usará para el problema resaltado anteriormente, es que su lógica de crear diferentes objetos (objetos de subclases) sea ​​específico para alguna condición comercial, por lo que en el futuro cuando necesite ampliar la funcionalidad de su biblioteca para otras plataformas (en términos más técnicos, debe agregar más subclases de interfaz básica o clase abstracta para que la fábrica devuelva esos objetos también además de uno existente basado en algunos parámetros dinámicos), cada vez que necesite cambiar (ampliar) la lógica de la clase de fábrica, que será una operación costosa y no buena desde la perspectiva del diseño. Por otro lado, si "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();
    }
}
Prakash Chhipa
fuente
15

También son útiles cuando necesita varios "constructores" con el mismo tipo de parámetro pero con un comportamiento diferente.

Rik
fuente
15

Es una buena idea usar métodos de fábrica dentro del objeto cuando:

  1. La clase del objeto no sabe qué subclases exactas tiene que crear
  2. La clase de objeto está diseñada para que los objetos que crea se especifiquen por subclases
  3. La clase del objeto delega sus deberes en subclases auxiliares y no sabe qué clase exacta tomará estos deberes

Es una buena idea utilizar la clase abstracta de fábrica cuando:

  1. Su objeto no debería depender de cómo se crean y diseñan sus objetos internos
  2. El grupo de objetos vinculados debe usarse juntos y debe cumplir esta restricción
  3. El objeto debe ser configurado por una de varias familias posibles de objetos vinculados que serán parte de su objeto padre
  4. Es necesario compartir objetos secundarios que muestren solo interfaces pero no una implementación
Dzianis Yafimau
fuente
9

UML de

ingrese la descripción de la imagen aquí

Producto: define una interfaz de los objetos que crea el método Factory.

ConcreteProduct: implementa la interfaz del producto

Creador: declara el método Factory

ConcreateCreator: implementa el método Factory para devolver una instancia de ConcreteProduct

Planteamiento del problema: cree una Fábrica de juegos utilizando los Métodos de fábrica, que definen la interfaz del juego.

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");
        }           
    }
}

salida:

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 ejemplo muestra una Factoryclase implementando a FactoryMethod.

  1. Gamees la interfaz para todo tipo de juegos. Define un método complejo:createGame()

  2. Chess, Ludo, Checkers son diferentes variantes de juegos, que proporcionan implementación para createGame()

  3. public Game getGame(String gameName)esta FactoryMethoden IGameFactoryclase

  4. GameFactorycrea previamente diferentes tipos de juegos en constructor. Implementa el IGameFactorymétodo de fábrica.

  5. El nombre del juego se pasa como argumento de línea de comando a NotStaticFactoryDemo

  6. getGamein GameFactoryacepta un nombre de juego y devuelve el Gameobjeto correspondiente .

Fábrica:

Crea objetos sin exponer la lógica de instanciación al cliente.

Método de fábrica

Defina una interfaz para crear un objeto, pero deje que las subclases decidan qué clase instanciar. El método Factory permite que una clase difiera la creación de instancias en subclases

Caso de uso:

Cuándo usarlo: Clientno sabe qué clases concretas será necesario crear en tiempo de ejecución, pero solo quiere obtener una clase que haga el trabajo.

Ravindra babu
fuente
gracias por su sección de notas clave, es conciso para mí. pero, es inimaginable que "getArea () sea el Método de Fábrica en la interfaz de Forma", porque el método getArea NUNCA instanciará ninguna clase, solo hace un cálculo. revise que "Defina un interfaz para crear un objeto, pero deje que las subclases decidan qué clase instanciar "por favor.
reco
getArea()No es un método de fábrica en absoluto .
Cranio
1
Tengo una opinión diferente: los expertos deben validar y poner notas. 1. El cliente (o el invocador) necesita un objeto de interés ... por lo tanto, no debería tener que llamar a GameFactory () más bien, la clase Factory debería tener un getInstance () 2. También si es así, games.put (Chess.class.getName ( ), nuevo Ajedrez ()); siempre devolverá la misma referencia de Ajedrez [si se implementa como estática]: ¿cómo manejar ese escenario de la manera más efectiva?
Arnab Dutta
He dado un ejemplo de fábrica no estática. Puede implementarlo con bloques y métodos estáticos si lo desea. Con respecto a sus consultas: 1. El cliente llamará a Factory para obtener el juego. 2. Estoy poniendo el objeto una vez y todo Get devolverá la misma instancia, la misma referencia.e de Chess se devuelve por cada get
Ravindra babu
6

Es realmente una cuestión de gustos. Las clases de fábrica se pueden abstraer / interconectar según sea necesario, mientras que los métodos de fábrica son más livianos (y también tienden a ser comprobables, ya que no tienen un tipo definido, pero requerirán un punto de registro conocido, similar a un servicio localizador pero para localizar métodos de fábrica).

Brad Wilson
fuente
4

Las clases de fábrica son útiles para cuando el tipo de objeto que devuelven tiene un constructor privado, cuando diferentes clases de fábrica establecen diferentes propiedades en el objeto de retorno, o cuando un tipo de fábrica específico se combina con su tipo concreto de retorno.

WCF usa las clases ServiceHostFactory para recuperar objetos ServiceHost en diferentes situaciones. IIS utiliza el ServiceHostFactory estándar para recuperar instancias de ServiceHost para archivos .svc , pero se usa un WebScriptServiceHostFactory para servicios que devuelven serializaciones a clientes JavaScript. ADO.NET Data Services tiene su propio DataServiceHostFactory especial y ASP.NET tiene su ApplicationServicesHostFactory ya que sus servicios tienen constructores privados.

Si solo tiene una clase que consume la fábrica, puede usar un método de fábrica dentro de esa clase.

Mark Cidade
fuente
2

Considere un escenario cuando tenga que diseñar una clase de Pedido y Cliente. Por simplicidad y requisitos iniciales, no siente la necesidad de fábrica para la clase de pedido y llene su solicitud con muchas declaraciones de 'nuevo pedido ()'. Las cosas funcionan bien.

Ahora aparece un nuevo requisito de que el objeto Order no se puede instanciar sin la asociación del Cliente (nueva dependencia). Ahora tiene las siguientes consideraciones.

1- Se crea una sobrecarga del constructor que funcionará solo para nuevas implementaciones. (Inaceptable). 2- Cambia las firmas de Order () y cambia cada invocación. (No es una buena práctica y un verdadero dolor).

En cambio, si ha creado una fábrica para la Clase de orden, solo tiene que cambiar una línea de código y está listo para comenzar. Sugiero la clase Factory para casi todas las asociaciones agregadas. Espero que ayude.

Muhammad Awais
fuente
1

si desea crear un objeto diferente en términos de uso. Es ú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;
      }
}
Samet ÖZTOPRAK
fuente
1

Cualquier clase que difiera la creación del objeto a su subclase para el objeto con el que necesita trabajar puede verse como un ejemplo del patrón Factory.

He mencionado en detalle en otra respuesta en https://stackoverflow.com/a/49110001/504133

nits.kk
fuente
1

Creo que dependerá del grado de acoplamiento suelto que desee aportar a su código.

El método de fábrica desacopla muy bien las cosas, pero la clase de fábrica no.

En otras palabras, es más fácil cambiar las cosas si usa el método de fábrica que si usa una fábrica simple (conocida como clase de fábrica).

Mira en este ejemplo: https://connected2know.com/programming/java-factory-pattern/ . Ahora, imagina que quieres traer un nuevo animal. En la clase Factory, debe cambiar Factory, pero en el método factory, no, solo necesita agregar una nueva subclase.

tajo
fuente
0

Las clases de fábrica son más pesadas, pero te dan ciertas ventajas. En los casos en que necesita construir sus objetos a partir de múltiples fuentes de datos sin procesar, le permiten encapsular solo la lógica de construcción (y tal vez la agregación de datos) en un solo lugar. Allí se puede probar en abstracto sin preocuparse por la interfaz del objeto.

He encontrado que este es un patrón útil, particularmente cuando no puedo reemplazar un ORM inadecuado y quiero instanciar eficientemente muchos objetos de uniones de tablas DB o procedimientos almacenados.

jonfm
fuente
0

Comparo las fábricas con el concepto de bibliotecas. Por ejemplo, puede tener una biblioteca para trabajar con números y otra para trabajar con formas. Puede almacenar las funciones de estas bibliotecas en directorios con nombre lógico como NumbersoShapes . Estos son tipos genéricos que podrían incluir enteros, flotadores, dobules, largos o rectángulos, círculos, triángulos, pentágonos en el caso de formas.

El petter de fábrica usa polimorfismo, inyección de dependencia e inversión de control.

El propósito declarado de los Patrones de Fábrica es: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Entonces, supongamos que está creando un sistema operativo o marco y está creando todos los componentes discretos.

Aquí hay un ejemplo simple del concepto del Patrón de Fábrica en PHP. Puede que no esté al 100% en todo, pero está destinado a servir como un ejemplo simple. No soy un experto.

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 );
Robert Rocha
fuente
Entiendo lo que está sucediendo aquí, pero no entiendo de qué sirve. ¿Qué NumbersFactory::makeNumber( 'float', 12.5 );me está dando más que decir new Float(12.5);si sé que necesito un Float? Esto es lo que no entiendo sobre las fábricas ... ¿cuál es el punto?
BadHorsie
Le permite elegir diferentes implementaciones y no lo vincula a una sola. Se establece una interfaz y todas las implementaciones deben garantizarla y honrarla.
Robert Rocha