En la programación orientada a objetos , el patrón de método de fábrica es un patrón de diseño que utiliza métodos de fábrica para tratar el problema de crear objetos sin tener que especificar sus clases exactas . En lugar de llamar a un constructor , esto se logra invocando un método de fábrica para crear un objeto. Los métodos de fábrica se pueden especificar en una interfaz e implementar mediante subclases o implementar en una clase base y, opcionalmente, anular mediante subclases. Es uno de los 23 patrones de diseño clásicos descritos en el libro Design Patterns (a menudo denominado "Gang of Four" o simplemente "GoF") y se subcategoriza como un patrón de creación . [1]
El patrón de diseño del método de fábrica resuelve problemas como:
Esto permite la creación de subclases que pueden cambiar la forma en que se crea un objeto (por ejemplo, redefiniendo qué clase instanciar).
Según Design Patterns: Elements of Reutilizable Object-Oriented Software : "Defina una interfaz para crear un objeto, pero permita que las subclases decidan qué clase instanciar. El método Factory permite que una clase posponga la instanciación que utiliza a las subclases". [2]
La creación de un objeto a menudo requiere procesos complejos que no son apropiados para incluirlos dentro de un objeto que lo compone. La creación del objeto puede llevar a una duplicación significativa de código, puede requerir información inaccesible para el objeto que lo compone, puede no proporcionar un nivel suficiente de abstracción o puede no estar incluida en las preocupaciones del objeto que lo compone . El patrón de diseño del método de fábrica maneja estos problemas definiendo un método separado para crear los objetos, que las subclases pueden anular para especificar el tipo derivado de producto que se creará.
El patrón del método de fábrica se basa en la herencia, ya que la creación de objetos se delega a subclases que implementan el método de fábrica para crear objetos. [3] El patrón también puede basarse en la implementación de una interfaz .
En el diagrama de clases UML anterior , la clase que requiere un objeto no crea una instancia de la clase directamente. En cambio, hace referencia a un objeto separado para crear un objeto de producto, lo que hace que sea independiente de la clase concreta exacta que se instancia. Las subclases de pueden redefinir qué clase instanciar. En este ejemplo, la subclase implementa lo abstracto al crear una instancia de la clase.Creator
Product
Product1
Creator
factoryMethod()
Creator
Creator
Creator1
factoryMethod()
Product1
Esta implementación de C++14 se basa en la implementación anterior a C++98 del libro. [5] [ ¿cuál? ]
#include <iostream> #include <memoria> enum ProductId { MIO , TUYO }; // define la interfaz de los objetos que crea el método de fábrica. class Producto { public : virtual void print () = 0 ; virtual ~ Producto () = default ; }; // implementa la interfaz Producto. clase ConcreteProductMINE : public Product { public : void print () { std :: cout << "this=" << this << " print MINE \n " ; } }; // implementa la interfaz Producto. clase ConcreteProductYOURS : public Product { public : void print () { std :: cout << "this=" << this << " print YOURS \n " ; } }; // declara el método de fábrica, que devuelve un objeto de tipo Producto. class Creator { public : virtual std :: unique_ptr < Product > create ( ProductId id ) { if ( ProductId :: MINE == id ) return std :: make_unique < ConcreteProductMINE > (); if ( ProductId :: YOURS == id ) return std :: make_unique < ConcreteProductYOURS > (); // repetir para los productos restantes... devolver nullptr ; } virtual ~ Creador () = predeterminado ; }; int main () { // unique_ptr evita fugas de memoria. std :: unique_ptr < Creador > creador = std :: make_unique < Creador > (); std :: unique_ptr < Producto > producto = creador -> crear ( ProductId :: MINE ); producto -> imprimir (); producto = creador -> crear ( ProductId :: TUYO ); producto -> imprimir (); }
La salida del programa es como
esto = 0x6e5e90 imprimir MIO esto = 0x6e62c0 imprimir TUYO
Un juego de laberinto se puede jugar en dos modos: uno con salas regulares que solo están conectadas con salas adyacentes y otro con salas mágicas que permiten a los jugadores transportarse al azar.
Room
es la clase base para un producto final ( MagicRoom
o OrdinaryRoom
). MazeGame
declara el método de fábrica abstracto para producir dicho producto base. MagicRoom
y OrdinaryRoom
son subclases del producto base que implementa el producto final. MagicMazeGame
y OrdinaryMazeGame
son subclases de MazeGame
implementación del método de fábrica que produce los productos finales. Los métodos de fábrica desacoplan así los llamadores ( MazeGame
) de la implementación de las clases concretas. Esto hace que el new
operador sea redundante, permite la adhesión al principio abierto-cerrado y hace que el producto final sea más flexible en caso de cambio.
// Vocabulario vacío del objeto actual interfaz pública IPerson { string GetName (); } clase pública Aldeano : IPerson { cadena pública GetName () { devolver "Persona del pueblo" ; } } clase pública CityPerson : IPerson { cadena pública GetName () { return "Persona de la ciudad" ; } } enumeración pública PersonType { Rural , Urban } /// <summary> /// Implementación de Factory - Se utiliza para crear objetos. /// </summary> public class PersonFactory { public IPerson GetPerson ( PersonType type ) { switch ( type ) { case PersonType.Rural : return new Villager (); case PersonType.Urban : return new CityPerson ( ); default : throw new NotSupportedException ( ) ; } } }
El código anterior muestra la creación de una interfaz llamada IPerson
y dos implementaciones llamadas Villager
y CityPerson
. Según el tipo que se pasa al PersonFactory
objeto, el objeto concreto original se devuelve como la interfaz IPerson
.
Un método de fábrica es simplemente un añadido a la PersonFactory
clase. Crea el objeto de la clase a través de interfaces, pero también permite que la subclase decida qué clase se instancia.
interfaz pública IProduct { cadena GetName (); bool SetPrice ( doble precio ); } clase pública Teléfono : IProducto { privada doble _precio ; cadena pública GetName () { devolver "Apple TouchPad" ; } público bool SetPrice ( double precio ) { _price = precio ; devuelve verdadero ; } } /* Casi igual que Factory, solo una exposición adicional para hacer algo con el método creado */ public abstract class ProductAbstractFactory { protected abstract IProduct MakeProduct (); public IProduct GetObject () // Implementación del método Factory. { return this.MakeProduct (); } } clase pública PhoneConcreteFactory : ProductAbstractFactory { protegida anulación IProduct MakeProduct () { IProduct producto = nuevo Phone (); // Hacer algo con el objeto después de recibirlo producto . SetPrice ( 20.30 ); devolver producto ; } }
En este ejemplo, MakeProduct
se utiliza en concreteFactory
. Como resultado, MakeProduct()
se puede invocar para recuperarlo de IProduct
. La lógica personalizada se puede ejecutar después de que se obtenga el objeto en el método de fábrica concreto. GetObject
se vuelve abstracto en la interfaz de fábrica.
Este ejemplo de Java es similar a uno del libro Patrones de diseño .
El MazeGame
método de usos Room
delega la responsabilidad de crear Room
objetos a sus subclases, que crean las clases concretas. El modo de juego normal podría utilizar este método de plantilla:
clase pública abstracta Habitación { void abstracto connect ( Habitación habitación ); } clase pública MagicRoom extiende Room { public void connect ( Room room ) {} } clase pública OrdinaryRoom extiende Room { public void connect ( Room room ) {} } clase abstracta pública MazeGame { lista final privada < Habitación > habitaciones = nueva ArrayList <> (); public MazeGame () { Habitación habitación1 = crearHabitación ( ) ; Habitación habitación2 = crearHabitación ( ) ; habitación1.conectar ( habitación2 ) ; habitaciones.añadir ( habitación1 ) ; habitaciones.añadir ( habitación2 ) ; } abstracto protegido Habitación makeRoom (); }
El MazeGame
constructor es un método de plantilla que agrega cierta lógica común. Hace referencia al método de fábrica que encapsula la creación de salas de modo que se puedan usar otras salas en una subclase. Para implementar el otro modo de juego que tiene salas mágicas, se puede anular makeRoom()
el método:makeRoom
clase pública MagicMazeGame extiende MazeGame { @Override protected MagicRoom makeRoom () { return new MagicRoom (); } } clase pública OrdinaryMazeGame extiende MazeGame { @Override protected OrdinaryRoom makeRoom () { return new OrdinaryRoom (); } } JuegoLaberintoJuegoOrdinario = new JuegoLaberintoOrdinario (); JuegoLaberintoJuegoMágico = new JuegoLaberintoMágico ( ) ;
Este ejemplo de PHP muestra implementaciones de interfaz en lugar de subclasificación (sin embargo, se puede lograr lo mismo mediante subclasificación). El método de fábrica también se puede definir public
y llamar directamente desde el código del cliente (a diferencia del ejemplo de Java anterior).
/* Interfaces de fábrica y de automóvil */interfaz CarFactory { función pública makeCar () : Car ; } interfaz Coche { función pública getType () : cadena ; } /*Implementaciones concretas de la fábrica y del automóvil*/la clase SedanFactory implementa CarFactory { función pública makeCar () : Car { devuelve nuevo Sedan (); } } La clase Sedan implementa Car { función pública getType () : string { return 'Sedan' ; } } /* Cliente */$fábrica = new SedanFactory (); $coche = $fábrica -> makeCar (); print $coche -> getType ();
Este ejemplo de Python emplea lo mismo que el ejemplo de Java anterior.
de abc importa ABC , método abstractoclase MazeGame ( ABC ): def __init __ ( self ) - > None : self.salas = [ ] self._prepare_salas ( ) def _preparar_salas ( self ) - > Ninguno : sala1 = self.hacer_sala ( ) sala2 = self.hacer_sala ( ) habitación1 . connect ( habitación2 ) self . rooms . append ( habitación1 ) self . rooms . append ( habitación2 ) def play ( self ) -> None : print ( f " Jugando usando { self.rooms [ 0 ] } " ) @abstractmethod def make_room ( self ): raise NotImplementedError ( "¡Deberías implementar esto!" )clase MagicMazeGame ( MazeGame ): def make_room ( self ) -> "MagicRoom" : return MagicRoom ()clase OrdinaryMazeGame ( MazeGame ): def make_room ( self ) -> "OrdinaryRoom" : return OrdinaryRoom ()clase Habitación ( ABC ) : def __init__ ( self ) -> None : self.connected_rooms = [ ] def connect ( self , room : " Room " ) - > None : self.connected_rooms.append ( room )clase MagicRoom ( Room ): def __str__ ( self ) -> str : return "Sala mágica"clase HabitacionOrdinaria ( Habitación ): def __str__ ( self ) -> str : return "Habitación ordinaria"juegoOrdinario = juegoLaberintoOrdinario ( ) juegoOrdinario.play ( )magicGame = MagicMazeGame () magicGame . play ()