it-swarm.it

Modello di fabbrica. Quando usare i metodi di fabbrica?

Quando è una buona idea usare i metodi di fabbrica all'interno di un oggetto invece di una classe di fabbrica?

249
jjshell

Mi piace pensare ai modelli di design in quanto le mie classi sono "persone" e gli schemi sono i modi in cui le persone parlano tra loro.

Quindi, per me il modello di fabbrica è come un'agenzia di assunzione. Hai qualcuno che avrà bisogno di un numero variabile di lavoratori. Questa persona potrebbe conoscere alcune informazioni di cui hanno bisogno nelle persone che assumono, ma il gioco è fatto.

Quindi, quando hanno bisogno di un nuovo dipendente, chiamano l'agenzia assumente e dicono loro di cosa hanno bisogno. Ora, per effettivamente assumere qualcuno, è necessario conoscere un sacco di cose - benefici, verifica dell'idoneità, ecc. Ma la persona che assume non ha bisogno di sapere nulla di tutto ciò - l'agenzia di assunzione si occupa di tutto ciò.

Allo stesso modo, l'utilizzo di una Factory consente al consumatore di creare nuovi oggetti senza dover conoscere i dettagli di come vengono creati o quali sono le loro dipendenze - devono solo fornire le informazioni che desiderano effettivamente.

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

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

Quindi, ora il consumatore di ThingFactory può ottenere una Cosa, senza dover conoscere le dipendenze della Cosa, tranne che per i dati di stringa che provengono dal consumatore.

355
kyoryu

I metodi di fabbrica dovrebbero essere considerati un'alternativa ai costruttori, soprattutto quando i costruttori non sono abbastanza espressivi, cioè.

class Foo{
  public Foo(bool withBar);
}

non è così espressivo come:

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

Le classi di fabbrica sono utili quando hai bisogno di un processo complicato per costruire l'oggetto, quando la costruzione ha bisogno di una dipendenza che non vuoi per la classe reale, quando hai bisogno di costruire oggetti diversi, ecc.

84
Rasmus Faber

Una situazione in cui personalmente trovo classi di Factory separate per dare un senso è quando l'oggetto finale che stai cercando di creare si basa su molti altri oggetti. E.g, in PHP: Supponiamo di avere un oggetto House, che a sua volta ha un Kitchen e un oggetto LivingRoom, e l'oggetto LivingRoom ha anche un oggetto TV

Il metodo più semplice per ottenere ciò è che ogni oggetto crei i propri figli sul loro metodo di costruzione, ma se le proprietà sono relativamente annidate, quando il House fallisce nella creazione probabilmente impiegherete del tempo a cercare di isolare esattamente ciò che sta fallendo.

L'alternativa è fare quanto segue (iniezione di dipendenza, se ti piace il termine di fantasia):

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

Qui se il processo di creazione di un House fallisce c'è solo un posto dove guardare, ma dover usare questo chunk ogni volta che si vuole un nuovo House è tutt'altro che conveniente. Entra nelle fabbriche:

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

Grazie alla fabbrica qui il processo di creazione di un House è astratto (nel senso che non è necessario creare e impostare ogni singola dipendenza quando si desidera creare un House) e allo stesso tempo centralizzato che rende più facile la manutenzione . Ci sono altri motivi per cui l'uso di fabbriche separate può essere utile (ad esempio, testabilità) ma trovo questo caso d'uso specifico per illustrare meglio come le classi di fabbrica possono essere utili.

66
Mahn

È importante differenziare chiaramente l'idea alla base dell'uso del metodo factory o factory . Entrambi sono pensati per affrontare i diversi tipi di problemi di creazione di oggetti che si escludono a vicenda.

Specifichiamo sul "metodo di produzione":

La prima cosa è che, quando si sviluppano le librerie o le API che a loro volta verranno utilizzate per lo sviluppo di ulteriori applicazioni, il metodo factory è una delle migliori selezioni per il modello di creazione. Ragione dietro; Sappiamo che quando si crea un oggetto delle funzionalità richieste, ma il tipo di oggetto rimane indeciso o si deciderà di passare i parametri dinamici .

Ora il punto è, approssimativamente lo stesso può essere ottenuto usando il modello di fabbrica stesso, ma un enorme inconveniente si introdurrà nel sistema se il modello di fabbrica sarà usato per il problema sopra evidenziato, è che la tua logica di raggruppamento di oggetti diversi (oggetti di sottoclassi) essere specifici per alcune condizioni di business, quindi in futuro quando è necessario estendere le funzionalità della libreria per altre piattaforme (in termini più tecnici, è necessario aggiungere più sottoclassi dell'interfaccia di base o di una classe astratta in modo che Factory possa restituire tali oggetti oltre a quello esistente basato su alcuni parametri dinamici) quindi ogni volta che è necessario modificare (estendere) la logica della classe di fabbrica che sarà costosa operazione e non buona dal punto di vista del progetto . Dall'altro lato, se verrà utilizzato il modello "metodo di fabbrica" per eseguire la stessa operazione, è sufficiente creare funzionalità aggiuntive (sottoclassi) e registrarle dinamicamente tramite l'iniezione, che non richiede modifiche nel codice 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();
    }
}
16
Prakash Chhipa

È una buona idea usare metodi factory inside object quando:

  1. La classe dell'oggetto non sa quali sottoclassi esatte deve creare
  2. La classe dell'oggetto è progettata in modo che gli oggetti creati vengano specificati dalle sottoclassi
  3. La classe di oggetti delega le sue funzioni alle sottoclassi ausiliarie e non sa quale classe esatto prenderà queste funzioni

È una buona idea usare abstract factory class quando:

  1. Il tuo oggetto non dovrebbe dipendere da come vengono creati e progettati i suoi oggetti interni
  2. Il gruppo di oggetti collegati deve essere usato insieme e devi servire questo vincolo
  3. L'oggetto dovrebbe essere configurato da una delle diverse famiglie possibili di oggetti collegati che faranno parte del tuo oggetto genitore
  4. È necessario condividere gli oggetti figlio che mostrano solo le interfacce ma non un'implementazione
14
Dzianis Yafimau

Sono anche utili quando servono diversi "costruttori" con lo stesso tipo di parametro ma con un comportamento diverso. 

14
Rik

UML da 

 enter image description here

Product: Definisce un'interfaccia degli oggetti creati dal metodo Factory.

ConcreteProduct: Implementa l'interfaccia del prodotto

Creatore: Dichiara il metodo Factory

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

Dichiarazione del problema: Crea una fabbrica di giochi usando 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(){

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

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
---------------------------------------
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 a createGame()

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

  4. GameFactory pre-crea diversi tipi di giochi nel costruttore. Implementa il metodo factory 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 Game corrispondente.

Factory:

Crea oggetti senza esporre la logica di istanziazione al client.

FactoryMethod

Definisci un'interfaccia per creare un oggetto, ma lascia che le sottoclassi decidano quale classe istanziare. Il metodo Factory consente a una classe di differire l'istanzazione alle sottoclassi

Caso d'uso:

Quando usarlo: Client non sa quali classi concrete sarà necessario creare in fase di runtime, ma vuole solo ottenere una classe che faccia il lavoro.

9
Ravindra babu

È davvero una questione di gusti. Le classi di fabbrica possono essere astratte/interfacciate via se necessario, mentre i metodi di fabbrica sono più leggeri (e tendono anche a essere testabili, poiché non hanno un tipo definito, ma richiedono un noto punto di registrazione, simile a un servizio localizzatore ma per localizzare i metodi di fabbrica).

5
Brad Wilson

Le classi di fabbrica sono utili quando il tipo di oggetto che restituiscono ha un costruttore privato, quando diverse classi factory impostano proprietà differenti sull'oggetto restituito, o quando un tipo di factory specifico è accoppiato con il suo tipo concreto di ritorno. 

WCFutilizza le classi ServiceHostFactory per recuperare gli oggetti ServiceHost in situazioni diverse. StandardHostFactory viene utilizzato da IIS per recuperare istanze ServiceHost per i file .svc, ma viene utilizzato un servizio WebScriptServiceHostFactory per i servizi che restituiscono le serializzazioni ai client JavaScript. ADO.NET Data Services ha un proprio DataServiceHostFactory speciale e ASP.NET ha la sua ApplicationServicesHostFactory poiché i suoi servizi hanno costruttori privati.

Se hai solo una classe che sta consumando la fabbrica, allora puoi semplicemente usare un metodo factory all'interno di quella classe.

4
Mark Cidade

Considera uno scenario quando devi progettare un ordine e una classe cliente. Per semplicità e requisiti iniziali non hai bisogno di factory per la classe Order e riempi la tua domanda con molte dichiarazioni 'new Order ()'. Le cose stanno andando bene.

Ora appare una nuova esigenza che l'oggetto Ordine non può essere istanziato senza l'associazione Cliente (nuova dipendenza). Ora hai le seguenti considerazioni.

1- Crei sovraccarico costruttore che funzionerà solo per nuove implementazioni. (Non accettabile) . 2- Si modificano le firme Order () e si modifica ogni singola chiamata. (Non una buona pratica e un vero dolore).

Invece se hai creato una factory per Order Class, devi solo cambiare una riga di codice e sei a posto. Suggerisco classe Factory per quasi tutte le associazioni aggregate. Spero che sia d'aiuto.

3
Muhammad Awais

Secondo il sito web di sourcemaking, le sue intenzioni sono:

  • Definisci un'interfaccia per creare un oggetto, ma lascia che le sottoclassi decidano quale classe istanziare. Il metodo di fabbrica consente a una classe di differire l'istanzazione alle sottoclassi.

  • Definire un costruttore "virtuale".

  • Il nuovo operatore ha ritenuto dannoso.

Un esempio di come può essere usato:

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

E poi i test:

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);
3
Alexander Beat

Qualsiasi classe che rimandi la creazione dell'oggetto alla sua sottoclasse per l'oggetto con cui deve lavorare può essere vista come un esempio di modello di fabbrica.

Ho menzionato in dettaglio in un'altra risposta a https://stackoverflow.com/a/49110001/504133

1
nits.kk

Mi piacciono le fabbriche al concetto di librerie. Ad esempio puoi avere una libreria per lavorare con i numeri e un'altra per lavorare con le forme. È possibile memorizzare le funzioni di queste librerie in directory denominate logicamente come Numbers o Shapes. Questi sono tipi generici che potrebbero includere numeri interi, float, dobules, long o rettangoli, cerchi, triangoli, pentagoni nel caso delle forme.

Il petter della fabbrica usa il polimorfismo, l'iniezione di dipendenza e l'inversione del controllo.

Lo scopo dichiarato dei Pattern di fabbrica è: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Quindi diciamo che stai costruendo un sistema operativo o un framework e stai costruendo tutti i componenti discreti.

Ecco un semplice esempio del concetto di Factory Pattern in PHP. Potrei non essere al 100% su tutto, ma è destinato a servire da semplice esempio. Non sono un esperto.

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 );
0
Robert Rocha

Le classi di fabbrica sono più pesanti, ma ti danno alcuni vantaggi. Nei casi in cui è necessario creare oggetti da più origini di dati grezzi, questi consentono di incapsulare solo la logica dell'edificio (e forse l'aggregazione dei dati) in un unico punto. Lì può essere testato in astratto senza preoccuparsi dell'interfaccia dell'oggetto.

Ho trovato questo uno schema utile, in particolare dove non sono in grado di sostituire e ORM inadeguato e voglio creare in modo efficiente molti oggetti da join di tabelle DB o stored procedure.

0
jonfm

Esempio di AbstractFactory.

    TypeImpl<String> type = new TypeImpl<>();
    type.addType("Condition");
    type.addType("Hazardous");

    AbstractTypeFactory<String, Tag> tags = new AbstractTypeFactory<String, Tag>(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");

}
0
Vahe Gharibyan

se vuoi creare un oggetto diverso in termini di utilizzo. È utile.

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;
      }
}
0
Samet öztoprak