Buscar..


Observaciones

Proporcionar una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas.

- GOF 1994

Fábrica simple (Java)

Una fábrica reduce el acoplamiento entre el código que necesita crear objetos a partir del código de creación de objetos. La creación de objetos no se hace explícitamente llamando a un constructor de clase, sino llamando a alguna función que crea el objeto en nombre del llamante. Un ejemplo simple de Java es el siguiente:

interface Car {
}

public class CarFactory{
  static public Car create(String s) {
    switch (s) {
    default:
    case "us":
    case "american": return new Chrysler();
    case "de":
    case "german": return new Mercedes();
    case "jp":
    case "japanese": return new Mazda();
    }
  }
}

class Chrysler implements Car {
  public String toString() { return "Chrysler"; }
}

class Mazda implements Car {
  public String toString() { return "Mazda"; }
}

class Mercedes implements Car {
  public String toString() { return "Mercedes"; }
}

public class CarEx {
  public static void main(String args[]) {
    Car car = CarFactory.create("us");
    System.out.println(car);
  }
}

En este ejemplo, el usuario solo da una pista sobre lo que necesita y la fábrica es libre de construir algo apropiado. Es una inversión de dependencia : el implementador del concepto de Car es libre de devolver un Car concreto apropiado solicitado por el usuario que a su vez no conoce los detalles del objeto concreto construido.

Este es un ejemplo simple de cómo funciona la fábrica; por supuesto, en este ejemplo, siempre es posible instanciar clases concretas; pero uno puede evitarlo ocultando clases concretas en un paquete, de manera que el usuario se vea obligado a usar la fábrica.

.Net Fiddle para el ejemplo anterior.

Fábrica abstracta (C ++)

El patrón abstracto de fábrica proporciona una manera de obtener una colección coherente de objetos a través de una colección de funciones de fábricas. En cuanto a cada patrón, el acoplamiento se reduce al abstraer la forma en que se crea un conjunto de objetos, de modo que el código de usuario no es consciente de los muchos detalles de los objetos que necesita.

El siguiente ejemplo de C ++ ilustra cómo obtener diferentes tipos de objetos de la misma familia de GUI (hipotética):

#include <iostream>

/* Abstract definitions */
class GUIComponent {
public:
  virtual ~GUIComponent() = default;
  virtual void draw() const = 0;
};
class Frame  : public GUIComponent {};
class Button : public GUIComponent {};
class Label  : public GUIComponent {};

class GUIFactory {
public:
  virtual ~GUIFactory() = default;
  virtual std::unique_ptr<Frame> createFrame() = 0;
  virtual std::unique_ptr<Button> createButton() = 0;
  virtual std::unique_ptr<Label> createLabel() = 0;
  static std::unique_ptr<GUIFactory> create(const std::string& type);
};

/* Windows support */
class WindowsFactory : public GUIFactory {
private:
    class WindowsFrame : public Frame {
    public:
      void draw() const override { std::cout << "I'm a Windows-like frame" << std::endl; }
    };
    class WindowsButton : public Button {
    public:
      void draw() const override { std::cout << "I'm a Windows-like button" << std::endl; }
    };
    class WindowsLabel : public Label {
    public:
      void draw() const override { std::cout << "I'm a Windows-like label" << std::endl; }
    };
public:
  std::unique_ptr<Frame> createFrame() override { return std::make_unique<WindowsFrame>(); }
  std::unique_ptr<Button> createButton() override { return std::make_unique<WindowsButton>(); }
  std::unique_ptr<Label> createLabel() override { return std::make_unique<WindowsLabel>(); }
};

/* Linux support */
class LinuxFactory : public GUIFactory {
private:
    class LinuxFrame : public Frame {
    public:
      void draw() const override { std::cout << "I'm a Linux-like frame" << std::endl; }
    };
    class LinuxButton : public Button {
    public:
      void draw() const override { std::cout << "I'm a Linux-like button" << std::endl; }
    };
    class LinuxLabel : public Label {
    public:
      void draw() const override { std::cout << "I'm a Linux-like label" << std::endl; }
    };
public:
  std::unique_ptr<Frame> createFrame() override { return std::make_unique<LinuxFrame>(); }
  std::unique_ptr<Button> createButton() override { return std::make_unique<LinuxButton>(); }
  std::unique_ptr<Label> createLabel() override { return std::make_unique<LinuxLabel>(); }
};

std::unique_ptr<GUIFactory> GUIFactory::create(const string& type) {
  if (type == "windows") return std::make_unique<WindowsFactory>();
  return std::make_unique<LinuxFactory>();
}

/* User code */
void buildInterface(GUIFactory& factory) {
  auto frame = factory.createFrame();
  auto button = factory.createButton();
  auto label = factory.createLabel();

  frame->draw();
  button->draw();
  label->draw();
}

int main(int argc, char *argv[]) {
  if (argc < 2) return 1;
  auto guiFactory = GUIFactory::create(argv[1]);
  buildInterface(*guiFactory);
}

Si el ejecutable generado se llama abstractfactory , la salida puede dar:

$ ./abstractfactory windows
I'm a Windows-like frame
I'm a Windows-like button
I'm a Windows-like label
$ ./abstractfactory linux  
I'm a Linux-like frame
I'm a Linux-like button
I'm a Linux-like label

Ejemplo simple de Factory que usa un IoC (C #)

Las fábricas también se pueden utilizar junto con las bibliotecas de Inversión de control (IoC).

  • El caso de uso típico de una fábrica de este tipo es cuando queremos crear un objeto basado en parámetros que no se conocen hasta el tiempo de ejecución (como el usuario actual).
  • En estos casos, a veces puede ser difícil (si no imposible) configurar la biblioteca IoC sola para manejar este tipo de información contextual en tiempo de ejecución, por lo que podemos envolverla en una fábrica.

Ejemplo

  • Supongamos que tenemos una clase de User , cuyas características (ID, nivel de autorización de seguridad, etc.) se desconocen hasta el tiempo de ejecución (ya que el usuario actual podría ser cualquiera que use la aplicación).
  • Necesitamos tomar el Usuario actual y obtener un ISecurityToken para ellos, que luego se puede usar para verificar si el usuario tiene permiso para realizar ciertas acciones o no.
  • La implementación de ISecurityToken variará dependiendo del nivel del Usuario; en otras palabras, ISecurityToken usa polimorfismo .

En este caso, tenemos dos implementaciones, que también utilizan interfaces de marcador para facilitar su identificación en la biblioteca IoC; La biblioteca IoC en este caso solo está formada e identificada por la abstracción IContainer .

Tenga en cuenta también que muchas fábricas modernas de IoC tienen capacidades o complementos nativos que permiten la creación automática de fábricas, además de evitar la necesidad de interfaces de marcador como se muestra a continuación; sin embargo, como no todos lo hacen, este ejemplo se adapta a un concepto de funcionalidad común más simple y más bajo.

//describes the ability to allow or deny an action based on PerformAction.SecurityLevel
public interface ISecurityToken
{
    public bool IsAllowedTo(PerformAction action);
}

//Marker interface for Basic permissions
public interface IBasicToken:ISecurityToken{};
//Marker interface for super permissions
public interface ISuperToken:ISecurityToken{};

//since IBasictoken inherits ISecurityToken, BasicToken can be treated as an ISecurityToken
public class BasicToken:IBasicToken
{
     public bool IsAllowedTo(PerformAction action)
     {
         //Basic users can only perform basic actions
         if(action.SecurityLevel!=SecurityLevel.Basic) return false;
         return true;
     }
}

public class SuperToken:ISuperToken
{
     public bool IsAllowedTo(PerformAction action)
     {
         //Super users can perform all actions         
         return true;
     }
}

A continuación, crearemos una fábrica de SecurityToken , que tomará como dependencia a nuestro IContainer

public class SecurityTokenFactory
{
   readonly IContainer _container;
   public SecurityTokenFactory(IContainer container)
   {
      if(container==null) throw new ArgumentNullException("container");
   }

   public ISecurityToken GetToken(User user)
   {
      if (user==null) throw new ArgumentNullException("user);
      //depending on the user security level, we return a different type; however all types implement ISecurityToken so the factory can produce them.
      switch user.SecurityLevel
      {
          case Basic:
           return _container.GetInstance<BasicSecurityToken>();
          case SuperUser:
           return _container.GetInstance<SuperUserToken>();
      }
   }
}

Una vez que hayamos registrado estos con el IContainer :

IContainer.For<SecurityTokenFactory>().Use<SecurityTokenFactory>().Singleton(); //we only need a single instance per app
IContainer.For<IBasicToken>().Use<BasicToken>().PerRequest(); //we need an instance per-request
IContainer.For<ISuperToken>().Use<SuperToken>().PerRequest();//we need an instance per-request 

el código consumidor puede usarlo para obtener el token correcto en tiempo de ejecución:

readonly SecurityTokenFactory _tokenFactory;
...
...
public void LogIn(User user)
{
    var token = _tokenFactory.GetToken(user);
    user.SetSecurityToken(token);
}

De esta manera, nos beneficiamos de la encapsulación provista por la fábrica y también de la administración del ciclo de vida provista por la biblioteca IoC.

Una fabrica abstracta

introduzca la descripción de la imagen aquí

El siguiente patrón de diseño se clasifica como un patrón creacional.

Se utiliza una fábrica abstracta para proporcionar una interfaz para crear familias de objetos relacionados, sin especificar clases concretas y se puede usar para ocultar clases específicas de la plataforma.

interface Tool {
    void use();
}

interface ToolFactory {
    Tool create();
}

class GardenTool implements Tool {

    @Override
    public void use() {
         // Do something...
    }
}

class GardenToolFactory implements ToolFactory {

    @Override
    public Tool create() {
        // Maybe additional logic to setup...
        return new GardenTool();
    }
}

class FarmTool implements Tool {

    @Override
    public void use() {
        // Do something...
    }
}

class FarmToolFactory implements ToolFactory {

    @Override
    public Tool create() {
        // Maybe additional logic to setup...
        return new FarmTool();
    }
}

Luego, se usaría un proveedor / productor de algún tipo al que se le pasaría información que le permitiría devolver el tipo correcto de implementación de fábrica:

public final class FactorySupplier {

    // The supported types it can give you...
    public enum Type {
        FARM, GARDEN
    };

    private FactorySupplier() throws IllegalAccessException {
        throw new IllegalAccessException("Cannot be instantiated");
    }

    public static ToolFactory getFactory(Type type) {

        ToolFactory factory = null;

        switch (type) {
        case FARM:
            factory = new FarmToolFactory();
            break;
        case GARDEN:
            factory = new GardenToolFactory();
            break;
        } // Could potentially add a default case to handle someone passing in null

        return factory;
    }
}

Ejemplo de fábrica implementando el método de fábrica (Java)

Intención:

Defina una interfaz para crear un objeto, pero deje que las subclases decidan qué clase crear una instancia. Método de fábrica permite que una clase difiera la creación de instancias a subclases.

Diagrama UML:

introduzca 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 de fábrica.

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

Declaración de problema: cree una Factory of Games usando 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(){
        createGame();
    }
    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(){
        createGame();
    }
    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(){
        createGame();
    }
    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]);
        System.out.println("Game="+game.getClass().getName());
    }
}

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
---------------------------------------
---------------------------------------
Create Checkers game
Opponents:2 or 3 or 4 or 6
For each opponent, place 10 coins
Start Checkers game
---------------------------------------
---------------------------------------
Create Ludo game
Opponents:2 or 3 or 4
For each opponent, place 4 coins
Create two dices with numbers from 1-6
Start Ludo game
---------------------------------------
Game=Chess

Este ejemplo muestra una clase Factory al implementar un FactoryMethod .

  1. Game es la interfaz para todo tipo de juegos. Se define el método complejo: createGame()

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

  3. public Game getGame(String gameName) es FactoryMethod en la clase IGameFactory

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

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

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

Cuándo usar:

  1. Fábrica : cuando no desea exponer la lógica de creación de instancias de objetos al cliente / llamante
  2. Abstract Factory : cuando desea proporcionar interfaz a familias de objetos relacionados o dependientes sin especificar sus clases concretas
  3. Método de fábrica: para definir una interfaz para crear un objeto, pero deje que las subclases decidan qué clase crear una instancia.

Comparación con otros patrones creacionales:

  1. El diseño comienza con Factory Method (menos complicado, más personalizable, las subclases proliferan) y evoluciona hacia Abstract Factory, Prototype o Builder (más flexible, más complejo) a medida que el diseñador descubre dónde se necesita más flexibilidad.

  2. Las clases abstractas de fábrica a menudo se implementan con métodos de fábrica , pero también se pueden implementar utilizando prototipos

Referencias para lectura adicional: Sourcemaking design-patterns

Fábrica de peso mosca (C #)

En palabras simples:

Una fábrica de peso mosca que para una clave dada, ya conocida, siempre dará el mismo objeto como respuesta. Para las nuevas claves creará la instancia y la devolverá.

Usando la fábrica:

ISomeFactory<string, object> factory = new FlyweightFactory<string, object>();

var result1 = factory.GetSomeItem("string 1");
var result2 = factory.GetSomeItem("string 2");
var result3 = factory.GetSomeItem("string 1");

//Objects from different keys
bool shouldBeFalse = result1.Equals(result2);

//Objects from same key
bool shouldBeTrue = result1.Equals(result3);

Implementación:

public interface ISomeFactory<TKey,TResult> where TResult : new()
{
    TResult GetSomeItem(TKey key);
}

public class FlyweightFactory<TKey, TResult> : ISomeFactory<TKey, TResult> where TResult : new()
{
    public TResult GetSomeItem(TKey key)
    {
        TResult result;
        if(!Mapping.TryGetValue(key, out result))
        {
            result = new TResult();
            Mapping.Add(key, result);
        }
        return result;
    }

    public Dictionary<TKey, TResult> Mapping { get; set; } = new Dictionary<TKey, TResult>();
}

Notas adicionales

Recomendaría agregar a esta solución el uso de un IoC Container (como se explica en un ejemplo diferente aquí) en lugar de crear sus propias nuevas instancias. Uno puede hacerlo agregando un nuevo registro para el TResult al contenedor y luego resolviéndolo (en lugar del dictionary en el ejemplo).

Método de fábrica

El patrón del método Factory es un patrón creacional que abstrae la lógica de creación de instancias de un objeto para desacoplar el código del cliente.

Cuando un método de fábrica pertenece a una clase que es una implementación de otro patrón de fábrica como Abstract factory , generalmente es más apropiado hacer referencia al patrón implementado por esa clase en lugar del patrón de método Factory.

El patrón del método de fábrica es más comúnmente referenciado cuando se describe un método de fábrica que pertenece a una clase que no es principalmente una fábrica.

Por ejemplo, puede ser ventajoso colocar un método de fábrica en un objeto que represente un concepto de dominio si ese objeto encapsula algún estado que simplifique el proceso de creación de otro objeto. Un método de fábrica también puede conducir a un diseño más alineado con el lenguaje ubicuo de un contexto específico.

Aquí hay un ejemplo de código:

//Without a factory method
Comment comment = new Comment(authorId, postId, "This is a comment");

//With a factory method
Comment comment = post.comment(authorId, "This is a comment");


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow