Ricerca…


Osservazioni

Fornire un'interfaccia per creare famiglie di oggetti correlati o dipendenti senza specificare le loro classi concrete.

- GOF 1994

Fabbrica semplice (Java)

Una factory riduce l'accoppiamento tra codice che deve creare oggetti dal codice di creazione dell'oggetto. La creazione dell'oggetto non viene effettuata in modo esplicito chiamando un costruttore di classi ma chiamando una funzione che crea l'oggetto per conto del chiamante. Un semplice esempio Java è il seguente:

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

In questo esempio, l'utente fornisce solo qualche indicazione su ciò di cui ha bisogno e la fabbrica è libera di costruire qualcosa di appropriato. È un'inversione di dipendenza : l'implementatore di Car concept è libero di restituire Car concreta appropriata richiesta dall'utente che a sua volta non conosce i dettagli dell'oggetto concreto costruito.

Questo è un semplice esempio di come funziona la fabbrica, ovviamente in questo esempio è sempre possibile istanziare classi concrete; ma si può prevenirlo nascondendo le classi concrete in un pacchetto, in modo tale che l'utente sia costretto a usare la fabbrica.

.Net Fiddle per esempio precedente.

Fabbrica astratta (C ++)

Il modello di fabbrica astratto fornisce un modo per ottenere una collezione coerente di oggetti attraverso una serie di funzioni di fabbrica. Come per ogni modello, l'accoppiamento si riduce astraendo il modo in cui viene creato un insieme di oggetti, in modo che il codice utente non sia a conoscenza dei molti dettagli degli oggetti di cui ha bisogno.

Il seguente esempio C ++ illustra come ottenere diversi tipi di oggetti della stessa (ipotetica) GUI:

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

Se l'eseguibile generato è denominato abstractfactory output può dare:

$ ./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

Semplice esempio di Factory che utilizza un IoC (C #)

Le fabbriche possono essere utilizzate insieme alle librerie Inversion of Control (IoC).

  • Il tipico caso d'uso per una tale fabbrica è quando vogliamo creare un oggetto basato su parametri che non sono noti fino al momento dell'esecuzione (come l'utente corrente).
  • In questi casi a volte può essere difficile (se non impossibile) configurare la libreria IoC da sola per gestire questo tipo di informazioni contestuali sul runtime, in modo che possiamo eseguire il wrapping in una fabbrica.

Esempio

  • Supponiamo di avere una classe User , le cui caratteristiche (ID, livello di sicurezza, ecc.) Sono sconosciute fino al runtime (poiché l'utente corrente potrebbe essere chiunque utilizzi l'applicazione).
  • Dobbiamo prendere l'utente corrente e ottenere un ISecurityToken per loro, che può quindi essere utilizzato per verificare se l'utente è autorizzato a eseguire determinate azioni o meno.
  • L'implementazione di ISecurityToken varierà a seconda del livello dell'utente - in altre parole, ISecurityToken utilizza il polimorfismo .

In questo caso, abbiamo due implementazioni, che utilizzano anche Marker Interfaces per semplificare l'identificazione con la libreria IoC; la libreria IoC in questo caso è appena costituita e identificata dall'astrazione IContainer .

Si noti inoltre che molte fabbriche IoC moderne hanno funzionalità native o plug-in che consentono l'auto-creazione di fabbriche oltre a evitare la necessità di interfacce marker come mostrato di seguito; tuttavia, poiché non tutti lo fanno, questo esempio si rivolge a un concetto di funzionalità comune semplice e più basso.

//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;
     }
}

Successivamente creeremo uno stabilimento SecurityToken , che avrà come dipendenza il nostro 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 volta registrati con 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 

il codice che consuma può usarlo per ottenere il token corretto in fase di runtime:

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

In questo modo beneficiamo dell'incapsulamento fornito dallo stabilimento e anche dalla gestione del ciclo di vita fornita dalla libreria IoC.

Una fabbrica astratta

inserisci la descrizione dell'immagine qui

Il seguente modello di progettazione è classificato come un modello creativo.

Una fabbrica astratta viene utilizzata per fornire un'interfaccia per la creazione di famiglie di oggetti correlati, senza specificare classi concrete e può essere utilizzata per nascondere classi specifiche della piattaforma.

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

Quindi sarebbe utilizzato un fornitore / produttore di qualche tipo che passerebbe informazioni che gli consentirebbero di restituire il tipo corretto di implementazione di fabbrica:

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

Esempio di fabbrica implementando il metodo Factory (Java)

Intento:

Definisci un'interfaccia per creare un oggetto, ma lascia che le classi secondarie decidano quale classe istanziare. Metodo di fabbrica consente a una classe di rinviare l'istanzazione alle sottoclassi.

Diagramma UML:

inserisci la descrizione dell'immagine qui

Prodotto: definisce un'interfaccia degli oggetti creati dal metodo Factory.

ConcreteProduct: implementa l'interfaccia del prodotto

Creatore: dichiara il metodo di fabbrica

ConcreateCreator: implementa il metodo Factory per restituire un'istanza di ConcreteProduct

Dichiarazione di problema: creare una fabbrica di giochi utilizzando i metodi di fabbrica, che definisce l'interfaccia di gioco.

Snippet di codice:

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

produzione:

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

Questo esempio mostra una classe Factory implementando un FactoryMethod .

  1. Game è l'interfaccia per tutti i tipi di giochi. Definisce il metodo complesso: createGame()

  2. Chess, Ludo, Checkers sono diverse varianti di giochi, che forniscono implementazione per createGame()

  3. public Game getGame(String gameName) è FactoryMethod nella classe IGameFactory

  4. GameFactory pre-crea diversi tipi di giochi nel costruttore. Implementa il metodo di fabbrica IGameFactory .

  5. Il nome del gioco viene passato come argomento della riga di comando a NotStaticFactoryDemo

  6. getGame in GameFactory accetta un nome di gioco e restituisce l'oggetto di Game corrispondente.

Quando usare:

  1. Fabbrica : quando non si desidera esporre la logica di istanziazione degli oggetti al client / chiamante
  2. Fabbrica astratta : quando si desidera fornire un'interfaccia alle famiglie di oggetti correlati o dipendenti senza specificare le loro classi concrete
  3. Metodo di fabbrica: per definire un'interfaccia per la creazione di un oggetto, ma lasciare che le sottoclassi decidano quale classe istanziare

Confronto con altri modelli creativi:

  1. Il design inizia con il metodo Factory (proliferano le sottoclassi meno complicate, più personalizzabili) e si evolve verso Abstract Factory, Prototype o Builder (più flessibile, più complesso) quando il designer scopre dove è necessaria più flessibilità

  2. Le classi Abstract Factory sono spesso implementate con Metodi Factory , ma possono anche essere implementate usando Prototype

Riferimenti per ulteriori letture: modelli di progettazione di Sourcemaking

Fabbrica di pesi volanti (C #)

In parole semplici:

Una fabbrica Flyweight che per una data chiave già conosciuta darà sempre lo stesso oggetto di risposta. Per le nuove chiavi creerà l'istanza e la restituirà.

Usando la fabbrica:

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

Implementazione:

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

Note extra

Raccomanderei di aggiungere a questa soluzione l'uso di un IoC Container (come spiegato in un altro esempio qui) anziché creare le proprie nuove istanze. Si può fare aggiungendo una nuova registrazione per TResult al contenitore e poi risolvendo da essa (invece del dictionary nell'esempio).

Metodo di fabbrica

Il pattern del metodo Factory è un pattern creazionale che astrae la logica di istanziazione di un oggetto al fine di separare il codice client da esso.

Quando un metodo factory appartiene a una classe che è un'implementazione di un altro pattern factory come Abstract factory , di solito è più appropriato fare riferimento al pattern implementato da quella classe piuttosto che al pattern del metodo Factory.

Lo schema del metodo Factory viene più comunemente utilizzato quando si descrive un metodo factory che appartiene a una classe che non è principalmente una factory.

Ad esempio, può essere vantaggioso posizionare un metodo factory su un oggetto che rappresenta un concetto di dominio se quell'oggetto incapsula uno stato che semplificherebbe il processo di creazione di un altro oggetto. Un metodo factory può anche portare a un design più allineato con l'Ubiquitous Language di uno specifico contesto.

Ecco un esempio di codice:

//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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow