Modello di fabbrica. Quando utilizzare i metodi di fabbrica?


Risposte:


386

Mi piace pensare al design dei pattens in termini di lezioni come "persone", e gli schemi sono i modi in cui le persone si parlano.

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

Quindi, quando hanno bisogno di un nuovo dipendente, chiamano l'agenzia di collocamento e dicono loro di cosa hanno bisogno. Ora, per assumere effettivamente qualcuno, devi conoscere un sacco di cose - benefici, verifica dell'ammissibilità, ecc. Ma la persona che assume non ha bisogno di sapere nulla di tutto ciò - l'agenzia di assunzione gestisce 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, ad eccezione dei dati di stringa che provengono dal consumatore.


17
Dove l'implementazione concreta di GetThing () recupera i valori di firstDependency e secondDependency?
Mikeyg36,

88
Qualcuno potrebbe dirmi come questo risponde alla domanda del PO? Questo semplicemente descrive cosa sia "Pattern di fabbrica" ​​e quindi aggiunge un esempio di "Metodo di fabbrica", che è solo uno dei tre "Pattern di fabbrica". In altre parole, non vedo paragoni da nessuna parte.
Pensatore

4
La domanda di OP menziona chiaramente within an object instead of a Factory class. Penso che intendesse lo scenario in cui rendi privato il ctor e usi un metodo statico per creare un'istanza della classe (creare un oggetto). Ma per seguire questo esempio bisogna prima istanziare la ThingFactoryclasse per ottenere Thingoggetti, il che rende questo Factory classin effetti.
atiyar il

4
Ci dispiace, ma la spiegazione è merda, perché un costruttore può anche essere scritto in modo da nascondere le dipendenze. Mancano le informazioni chiave di base che si desidera separare le informazioni sulla creazione della dipendenza dalla gestione delle dipendenze. Inoltre, la domanda riguardava la stessa classe, la risposta non è in alcun modo collegata a quella.
Christian Hujer,

8
OP ha chiesto quando . Kyoryu ha risposto come . Sebbene lo stile della risposta sia encomiabile, nel contesto di questa domanda, è solo rumore.
8bitjunkie,

96

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

class Foo{
  public Foo(bool withBar);
}

non è espressivo come:

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

Le classi di fabbrica sono utili quando è necessario un processo complicato per la costruzione dell'oggetto, quando la costruzione richiede una dipendenza che non si desidera per la classe effettiva, quando è necessario costruire oggetti diversi ecc.


2
Dov'è la classe Factory qui?
Koray Tugay,

20
@KorayTugay: non esiste una classe factory, solo metodi factory. La domanda riguarda quando utilizzare i metodi di fabbrica anziché una classe di fabbrica. Ma i metodi di fabbrica sono più un'alternativa ai costruttori che un'alternativa alle classi di fabbrica. (Non so perché la risposta migliore sia valutata così bene nonostante si parli solo di classi di fabbrica).
Rasmus Faber,

5
Va notato che i metodi statici di fabbrica sono completamente diversi dal modello di progettazione Gang of Four: Factory Method.
jaco0646,

76

Una situazione in cui trovo personalmente diverse classi Factory per dare un senso è quando l'oggetto finale che stai cercando di creare si basa su molti altri oggetti. Ad esempio, in PHP: Supponiamo di avere un Houseoggetto, che a sua volta ha un Kitchene un LivingRoomoggetto, e l' LivingRoomoggetto ha anche un TVoggetto all'interno.

Il metodo più semplice per raggiungere questo obiettivo è far sì che ogni oggetto crei i propri figli sul loro metodo di costruzione, ma se le proprietà sono relativamente nidificate, quando la Housecreazione non riesce, probabilmente passerai un po 'di tempo a cercare di isolare esattamente ciò che sta fallendo.

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

$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 Housefallisce c'è solo un posto dove cercare, ma dover usare questo pezzo ogni volta che si desidera un nuovo Houseè tutt'altro che conveniente. Inserisci le 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 (in quanto non è necessario creare e impostare ogni singola dipendenza quando si desidera solo creare un House) e allo stesso tempo centralizzato che ne facilita la manutenzione. Esistono altri motivi per cui l'utilizzo di Fabbriche separate può essere utile (ad esempio testabilità), ma trovo questo caso d'uso specifico per illustrare come le classi Factory possono essere utili.


1
Come farebbe qualcuno a fare un test unitario su questo? Pensavo che usare la "nuova" parola chiave in una classe fosse considerata una cattiva pratica perché non può essere testata dall'unità. O una fabbrica vuole essere un po 'un'eccezione a quella regola?
AgmLauncher,

1
@AgmLauncher Ho avuto la stessa domanda anche quando ho iniziato con i test unitari, controlla: stackoverflow.com/questions/10128780/…
Mahn

1
Non ho capito. In che modo esattamente i parametri per la creazione di oggetti diversi vengono passati alla HouseFactoryclasse?
atiyar

1
@Mahn, alla fine non avresti avuto molti parametri?
Pacerier,

1
@Pacerier è qualcosa che devi decidere come modellare, in base alle tue esigenze, ma non devi sempre passare tutti i parametri al createmetodo. Ad esempio, se hai Housesempre lo stesso tipo, LivingRoomallora ha senso avere i suoi parametri hardcoded nella classe factory piuttosto che passare come argomenti. Oppure potresti voler fornire un typeargomento al tuo HouseFactory::createmetodo se hai alcuni tipi di se LivingRoomhai un interruttore con i parametri hardcoded per ogni tipo.
Mahn,

19

È importante differenziare chiaramente l'idea alla base del metodo factory o factory. Entrambi hanno lo scopo di affrontare diversi tipi di problemi di creazione di oggetti reciprocamente esclusivi.

Precisiamo il "metodo di fabbrica":

La prima cosa è che, quando si sviluppano librerie o API che a loro volta verranno utilizzate per un ulteriore sviluppo dell'applicazione, il metodo factory è una delle migliori selezioni per il modello di creazione. Motivo dietro; Sappiamo che quando creare un oggetto con le funzionalità richieste ma il tipo di oggetto rimarrà indeciso o verrà deciso di passare parametri dinamici .

Ora il punto è che si può ottenere approssimativamente lo stesso utilizzando lo stesso modello di fabbrica, ma un enorme svantaggio introdurrà nel sistema se il modello di fabbrica verrà utilizzato per il problema sopra evidenziato, è che la tua logica di gettare oggetti diversi (oggetti delle sottoclassi) essere specifici per alcune condizioni aziendali, quindi in futuro quando è necessario estendere le funzionalità della libreria per altre piattaforme (in termini più tecnici, è necessario aggiungere più sottoclassi di interfaccia di base o classe astratta in modo che factory restituisca tali oggetti anche oltre a quello esistente sulla base di alcuni parametri dinamici) quindi ogni volta che è necessario modificare (estendere) la logica della classe di fabbrica che sarà un'operazione costosa e non buona dal punto di vista del design. Dall'altro lato, se "metodo di fabbrica"

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

15

Sono utili anche quando sono necessari più "costruttori" con lo stesso tipo di parametro ma con comportamento diverso.


15

È consigliabile utilizzare i metodi di fabbrica all'interno dell'oggetto quando:

  1. La classe Object non sa quali sottoclassi esatte debba creare
  2. La classe Object è progettata in modo tale che gli oggetti creati siano stati specificati da sottoclassi
  3. La classe di Object delega i propri compiti a sottoclassi ausiliarie e non sa quale classe esatta assumerà tali compiti

È una buona idea utilizzare la classe di fabbrica astratta 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 ed è necessario soddisfare questo vincolo
  3. L'oggetto deve essere configurato da una delle diverse possibili famiglie di oggetti collegati che faranno parte dell'oggetto principale
  4. È necessario condividere oggetti figlio che mostrano solo interfacce ma non un'implementazione

9

UML da

inserisci qui la descrizione dell'immagine

Prodotto: 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 Factory of Games utilizzando Factory Methods, 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 Factoryclasse implementando a FactoryMethod.

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

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

  3. public Game getGame(String gameName)è FactoryMethodin IGameFactoryclasse

  4. GameFactorycrea diversi tipi di giochi nel costruttore. Implementa il IGameFactorymetodo di fabbrica.

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

  6. getGamein GameFactoryaccetta un nome del gioco e torna corrispondenti Gameoggetto.

Fabbrica:

Crea oggetti senza esporre la logica di istanza al client.

FactoryMethod

Definire un'interfaccia per la creazione di un oggetto, ma lasciare che le sottoclassi decidano quale classe creare un'istanza. Il metodo Factory consente a una classe di rinviare l'istanza alle sottoclassi

Caso d'uso:

Quando usare: Clientnon sa quali classi concrete sarà necessario creare in fase di esecuzione, ma vuole solo ottenere una classe che farà il lavoro.


grazie per la tua sezione di note chiave, è conciso per me. ma è inimmaginabile che "getArea () sia il metodo Factory nell'interfaccia Shape", perché il metodo getArea non istanza MAI alcuna classe, fa semplicemente un calcolo. osserva che "Definisci un interfaccia per la creazione di un oggetto, ma lascia che le sottoclassi decidano quale classe istanziare "per favore.
Reco

getArea()non è un metodo factory a tutti .
Cranio,

1
Ho un'opinione diversa: per favore, gli esperti convalidano e mettono note. 1. Il cliente (o il chiamante) ha bisogno di un oggetto di interesse ... quindi non dovrebbe essere necessario chiamare il nuovo GameFactory () piuttosto la classe Factory dovrebbe avere un getInstance () 2.In caso affermativo, games.put (Chess.class.getName ( ), new Chess ()); restituirà sempre lo stesso riferimento di scacchi [se implementato come statico] - come gestire lo scenario nel modo più efficace?
Arnab Dutta,

Ho dato un esempio di fabbrica non statica. Se lo desideri, puoi implementarlo con blocchi e metodi statici. Per quanto riguarda le tue domande: 1. Il cliente chiamerà Factory per ottenere il gioco. 2. Metto l'oggetto una volta e tutti Get restituiranno la stessa istanza - lo stesso riferimento di Chess viene restituito per ogni get
Ravindra babu

6

È davvero una questione di gusti. Le classi di fabbrica possono essere astratte / interfacciate 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 punto di registrazione ben noto, simile a un servizio localizzatore ma per individuare i metodi di fabbrica).


4

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

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

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


2

Prendi in considerazione uno scenario in cui devi progettare un ordine e una classe cliente. Per semplicità e requisiti iniziali, non sentite la necessità della fabbrica per la classe Order e riempite la vostra applicazione con molte dichiarazioni "new Order ()". Le cose stanno funzionando bene.

Ora emerge un nuovo requisito secondo cui l'oggetto Ordine non può essere istanziato senza l'associazione del cliente (nuova dipendenza). Ora hai le seguenti considerazioni.

1- Si crea un sovraccarico del costruttore che funzionerà solo per le nuove implementazioni. (Non accettabile). 2- Si cambiano le firme dell'Ordine () e si cambia ogni singolo invito. (Non è una buona pratica e un vero dolore).

Invece se hai creato una fabbrica per la classe di ordine devi solo cambiare una riga di codice e sei a posto. Suggerisco la classe Factory per quasi tutte le associazioni aggregate. Spero che aiuti.


1

se si desidera 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;
      }
}


1

Penso che dipenderà dal grado di accoppiamento lento che vuoi portare al tuo codice.

Il metodo di fabbrica disaccoppia le cose molto bene ma la classe di fabbrica no.

In altre parole, è più facile cambiare le cose se si utilizza il metodo factory che se si utilizza una fabbrica semplice (nota come classe factory).

Guarda in questo esempio: https://connected2know.com/programming/java-factory-pattern/ . Ora, immagina di voler portare un nuovo animale. Nella classe Factory è necessario modificare Factory ma nel metodo factory, no, è sufficiente aggiungere una nuova sottoclasse.


0

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

Ho trovato questo un modello utile, in particolare dove non sono in grado di sostituire e inadeguato ORM e voglio creare un'istanza efficiente di molti oggetti dai join di tabelle DB o dalle procedure memorizzate.


0

Ho paragonato le fabbriche al concetto di biblioteche. 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 con nome logico come NumbersoShapes . Questi sono tipi generici che potrebbero includere numeri interi, float, dobuli, lunghe o rettangoli, cerchi, triangoli, pentagoni nel caso di forme.

Il petter di fabbrica utilizza polimorfismo, iniezione di dipendenza e inversione di controllo.

Lo scopo dichiarato dei modelli 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.

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

Capisco cosa sta succedendo qui, ma non capisco che senso abbia. Cosa NumbersFactory::makeNumber( 'float', 12.5 );mi sta dando semplicemente dicendo new Float(12.5);se so di aver bisogno di un Float? Questo è ciò che non capisco delle fabbriche ... qual è il punto?
BadHorsie,

Ti consente di scegliere diverse implementazioni e non ti lega a una sola. Viene stabilita un'interfaccia e tutte le implementazioni devono garantirla e onorarla.
Robert Rocha,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.