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

¿Cuándo es una buena idea usar métodos de fábrica dentro de un objeto en lugar de una clase de fábrica?

    Me gusta pensar en patrones de diseño en términos de que mis clases son ‘personas’, y los patrones son las formas en que las personas hablan entre sí.

    Entonces, para mí, el patrón de fábrica es como una agencia de contratación. Tienes a alguien que necesitará una cantidad variable de trabajadores. Esta persona puede saber algo de información que necesitan en las personas que contratan, 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 lo contrata no necesita saber nada de esto: la agencia contratante maneja todo eso.

    Del mismo modo, el uso de Factory permite al consumidor crear nuevos objetos sin tener que conocer los detalles de cómo se crearon, ni 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); } } 

    Por lo tanto, ahora el consumidor de ThingFactory puede obtener una cosa, sin tener que saber sobre las dependencias de la cosa, a excepción de los datos de cadena que provienen del consumidor.

    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 objetos diferentes, etc.

    Una situación en la que personalmente encuentro que las clases de Factory separadas tienen sentido es cuando el objeto final que intentas crear depende de varios otros objetos. Por ejemplo, en PHP: supongamos que tiene un objeto House , que a su vez tiene un objeto Kitchen y un LivingRoom , y el objeto LivingRoom tiene un objeto de TV adentro.

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

    La alternativa es hacer lo siguiente (dependency injection, si te 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 House falla, solo hay un lugar para mirar, pero tener que usar este pedazo cada vez que uno quiere una nueva House está lejos de ser conveniente. Ingrese 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 House se 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 el mantenimiento. . Existen otras razones por las que el uso de fábricas separadas puede ser beneficioso (por ejemplo, la capacidad de prueba), pero este caso de uso específico me sirve para ilustrar mejor cómo las clases de fábrica pueden ser útiles.

    Es importante diferenciar claramente la idea detrás de usar el método de fábrica o de fábrica. Ambos están destinados a abordar problemas de creación de objetos de diferente tipo mutuamente excluyentes.

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

    Lo primero es que, cuando desarrolle bibliotecas o API que a su vez se utilizarán para el desarrollo de aplicaciones adicionales, 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 funcionalidad requerida pero el tipo de objeto permanecerá indeciso o se decidirá si se pasan los parámetros dynamics .

    Ahora el punto es que aproximadamente se puede lograr el mismo utilizando el patrón de fábrica en sí, pero un gran inconveniente se introducirá en el sistema si se usa el patrón de fábrica para el problema resaltado anteriormente, es que su lógica de encajonar diferentes objetos (objetos de subclase) sea ​​específico para algunas condiciones comerciales, de modo que en el futuro 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 factory devuelva esos objetos además del existente) basado en algunos parámetros dynamics), entonces 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 se usa el patrón “método de fábrica” ​​para realizar lo mismo, solo necesita crear una funcionalidad adicional (subclases) y registrarlo dinámicamente mediante inyección, lo que no requiere cambios en su código base.

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

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

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

    1. La clase del objeto no sabe qué subclases exactas debe crear
    2. La clase del objeto está diseñada para que los objetos que crea estén especificados por subclases
    3. La clase del objeto delega sus deberes a las subclases auxiliares y no sabe qué clase exacta tomará estos deberes

    Es una buena idea usar clases abstractas de fábrica cuando:

    1. Su objeto no debe depender de cómo se crean y diseñan sus objetos internos
    2. El grupo de objetos vinculados debe usarse en conjunto y debe cumplir esta restricción
    3. El objeto debe ser configurado por una de varias familias posibles de objetos vinculados que formarán parte de su objeto primario
    4. Se requiere compartir objetos secundarios que muestren solo interfaces pero no una implementación

    UML de

    enter image description here

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

    ConcreteProduct: implementa la interfaz del producto

    Creador: declara el método de Fábrica

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

    Declaración del problema: cree una fábrica de juegos utilizando Factory Methods, que define 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 games = new HashMap(); /* 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 clase de Factory implementando FactoryMethod .

    1. Game es la interfaz para todo tipo de juegos. Define el 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) es FactoryMethod en la clase IGameFactory

    4. GameFactory pre-crea diferentes tipos de juegos en el constructor. Implementa el método de fábrica IGameFactory .

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

    6. getGame en GameFactory acepta el nombre de un juego y devuelve el correspondiente objeto del Game .

    Fábrica:

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

    FactoryMethod

    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 instanciación a subclases

    Caso de uso:

    Cuándo usar: el Client no sabe qué clases concretas se le requerirá crear en tiempo de ejecución, pero solo quiere obtener una clase que haga el trabajo.

    Es realmente una cuestión de gusto. Las clases de fábrica pueden abstraerse / interconectarse 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).

    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 propiedades diferentes en el objeto de devolución, o cuando un tipo de fábrica específico está acoplado con su tipo de hormigón de retorno.

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

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

    Considere un escenario cuando debe diseñar una clase de Orden y Cliente. Para simplificar y cumplir los requisitos iniciales, no necesita la fábrica para la clase de pedido y completa su solicitud con muchas ‘nuevas declaraciones de pedido ()’. Las cosas están funcionando bien.

    Ahora aparece un nuevo requisito en la imagen de que el objeto Order no puede crearse una instancia sin la asociación del Cliente (nueva dependencia). Ahora tienes las siguientes consideraciones.

    1- Crea sobrecarga de constructor que solo funcionará para nuevas implementaciones. (Inaceptable). 2- Cambias las firmas Order () y cambias todas y cada una de las invokation. (No es una buena práctica y un dolor real).

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

    De acuerdo con el sitio web de creación de fonts, sus intenciones son:

    • Defina una interfaz para crear un objeto, pero deje que las subclases decidan a qué clase instanciar. El método de fábrica permite que una clase difiera la instanciación a subclases.

    • Definición de un constructor “virtual”.

    • El nuevo operador consideró dañino.

    Un ejemplo de cómo se puede usar:

     abstract class AbstractFactoryMethod { abstract function makePHPBook($param); } class OReillyFactoryMethod extends AbstractFactoryMethod { function makePHPBook($param) { $book = NULL; switch ($param) { case "us": $book = new OReillyPHPBook(); break; // Other classes... case "other": $book = new SamsPHPBook(); break; default: $book = new OReillyPHPBook(); break; } return $book; } 

    Y luego la prueba:

     function testFactoryMethod($factoryMethodInstance) { $phpUs = $factoryMethodInstance->makePHPBook("us"); echo 'us php Author: '.$phpUs->getAuthor(); echo 'us php Title: '.$phpUs->getTitle(); } echo 'Testing OReillyFactoryMethod'; $factoryMethodInstance = new OReillyFactoryMethod(); testFactoryMethod($factoryMethodInstance); 

    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 fonts de datos sin procesar, le permiten encapsular solo la lógica de construcción (y tal vez la agregación de los datos) en un solo lugar. Allí puede probarse en abstracto sin preocuparse por la interfaz del objeto.

    He encontrado que este es un patrón útil, particularmente cuando no puedo reemplazar y un ORM inadecuado y quiero crear una instancia eficiente de muchos objetos a partir de combinaciones de tablas DB o procedimientos almacenados.

    Yo comparo 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 nombres lógicos como Numbers o Shapes . Estos son tipos generics que pueden incluir enteros, flotantes, dobules, largos o rectangularjs, círculos, triangularjs, pentágonos en el caso de las formas.

    El petter de fábrica usa polymorphism, dependency injection 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. 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, digamos que está construyendo un Sistema Operativo o Marco y está construyendo todos los componentes discretos.

    Aquí hay un ejemplo simple del concepto de patrón de fábrica en PHP. Puede que no esté al 100% en todo, pero está destinado a servir como un simple ejemplo. 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 ); 

    Ejemplo de AbstractFactory.

      TypeImpl type = new TypeImpl<>(); type.addType("Condition"); type.addType("Hazardous"); AbstractTypeFactory tags = new AbstractTypeFactory(type) { @Override public Tag create(String string) { String tp = type.find(string); switch (tp) { case "Hazardous": return new HazardousTag(); case "Condition": return new ConditionTag(); default: return null; } } }; Tag tagHazardous = tags.create("Hazardous"); Tag tagCondition = tags.create("Condition"); } 

    si quieres 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; } } 

    Cualquier clase que difiera la creación del objeto a su subclase para el objeto con el que necesita trabajar se puede ver como un ejemplo de patrón de fábrica.

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