2009-03-05 2 views
7

Uno dei modi per implementare correttamente Dipendenza Iniezione è quello di separare la creazione dell'oggetto dalla logica aziendale. In genere, ciò comporta l'utilizzo di una Factory per la creazione di oggetti.È OK passare i parametri ad un metodo di fabbrica?

Fino a questo punto, non ho mai seriamente pensato di utilizzare una fabbrica quindi mi scuso se questa domanda sembra un po 'riduttivo:

In tutti gli esempi del modello di fabbrica che ho imbattuto in, ho sempre vedi esempi molto semplici che non hanno parametrizzazione. Ad esempio, ecco una fabbrica rubata da Misko Hevery's articolo eccellente How To Think About the "new" Operator.

 
class ApplicationBuilder { 
    House build() { 
    return new House(new Kitchen(
       new Sink(), 
       new Dishwasher(), 
       new Refrigerator()) 
      ); 
    } 
} 

Tuttavia, cosa succede se voglio che ogni casa che costruisco abbia un nome? Sto ancora usando il pattern Factory se riscrivo questo codice come segue?

 
class ApplicationBuilder { 
    House build(const std::string & house_name) { 
    return new House(house_name, 
         new Kitchen(new Sink(), 
            new Dishwasher(), 
            new Refrigerator()) 
        ); 
    } 
} 

Nota che il mio metodo di fabbrica chiamata è cambiato da questo:

 
ApplicationBuilder builder; 
House * my_house = builder.build();

A tal:

 
ApplicationBuilder builder; 
House * my_house = builder.build("Michaels-Treehouse");

A proposito: Penso che il concetto di separazione oggetto istanziare dalla logica di business è fantastico, sto solo cercando di capire come posso applicarlo alla mia situazione. Ciò che mi confonde è che tutti gli esempi che ho visto del pattern Factory non passano mai alcun parametro nella funzione build().

Per essere chiari: non conosco il nome della casa fino al momento prima ho bisogno di istanziarlo.

+0

Ho visto spesso le fabbriche prendere i parametri come hai mostrato. Non c'è niente di sbagliato in questo. O in termini di domanda. È perfettamente ok – grieve

risposta

8

Ho visto un sacco di esempi che usano un set fisso di argomenti, come nel tuo esempio di nome, e li ho usati anch'io e non riesco a vedere nulla di sbagliato in esso.

Tuttavia v'è una buona ragione che molti tutorial o piccoli articoli eviterà di fabbriche che inoltrano parametri agli oggetti costruiti: È praticamente impossibile inoltrare numero arbitrario di argomenti (anche per un limite sano come 6 argomenti). Ogni parametro inoltrato deve essere accettato come const T& e T& se si desidera farlo generico.

Per esempi più complessi, tuttavia, è necessario un set crescita esponenziale di sovraccarichi (per ogni parametro, un const e una versione nonconst) e perfect forwarding non è possibile a tutti (in modo che i provvisori vengono inoltrati come provvisori, per esempio) . Per il prossimo standard C++ che problema è risolto:

class ApplicationBuilder { 
    template<typename... T> 
    House *build(T&&... t) { 
    return new House(std::forward<T>(t)..., 
         new Kitchen(new Sink(), 
            new Dishwasher(), 
            new Refrigerator()) 
        ); 
    } 
}; 

In questo modo, è possibile chiamare

builder.build("Hello", 13); 

e tornerà

new House("Hello", 13, new Kitchen(new Sink(... 

Leggi l'articolo che ho linkato sopra.

+0

Bel link sui riferimenti rvalue! Probabilmente è al di là della portata della domanda, ma è uno degli articoli più chiari che ho letto sull'argomento. –

5

Non riesco a capire perché sarebbe sbagliato aggiungere questo parametro alla fabbrica. Ma tieni presente che non dovresti finire per aggiungere molti parametri che potrebbero non essere utili a tutti gli oggetti creati dalla fabbrica. Se lo fai, avrai perso molti dei vantaggi di una fabbrica!

+0

Descrivendo il tipo di oggetto desiderato (nei parametri), questo può consentire alla fabbrica di restituire oggetti completamente diversi (restituendo una classe base o un'interfaccia). – Aardvark

1

Sono d'accordo con Benoit. Pensa ad una fabbrica per la creazione di qualcosa come le connessioni sql, tuttavia, in un caso come questo, sarebbe necessario passare informazioni sulla connessione alla fabbrica. La fabbrica utilizzerà queste informazioni per utilizzare il protocollo server corretto e così via.

4

L'idea di una fabbrica è che ti dà un'istanza di una classe/interfaccia, quindi non c'è nulla di male nel passare i parametri. Se ci fosse, sarebbe male passare anche i parametri a un nuovo().

5

Non solo è accettabile, ma è comune per passare i parametri a un metodo di fabbrica. Controlla some examples. Normalmente il parametro è un tipo che dice alla fabbrica cosa fare, ma non c'è motivo per cui non si possano aggiungere altre informazioni necessarie per costruire un oggetto. Penso che quello che stai facendo va bene.

1

Certo, perché no ..!?

Il bello dei parametri di trasmissione è che consente di nascondere l'implementazione dell'oggetto concreto. Ad esempio, nel codice che hai postato si passano i parametri al costruttore. Tuttavia, è possibile modificare l'implementazione in modo che vengano passati tramite un metodo Initiailze. Passando i parametri al metodo factory si nasconde la natura della costruzione e dell'inizializzazione dell'oggetto dal chiamante.

1

Dai un'occhiata a Loki :: Factory, c'è un'implementazione che piace molto anche a Boost, comunque. Qualche esempio di codice che uso regolarmente in diversi gusti:

typedef Loki :: SingletonHolder < Loki :: Fabbrica < Component, std :: string, Loki :: Typelist < const DataCollection &, Loki :: Typelist < gioco *, Loki :: NullType>>>> ComponentFactory;

Questo potrebbe sembrare un po 'strano a prima vista, tuttavia lasciatemi spiegare questa bestia e quanto sia potente. Fondamentalmente creiamo un singleton che contiene una factory, la maggior parte dei parametri sono per il singleton, Component è il nostro prodotto, std :: string è il nostro tipo di id di creazione, dopo questo segue una lista di tipi dei parametri che è richiesta per la creazione di Components (questo può essere definito usando anche una macro per una sintassi meno dettagliata). Dopo questa riga si può semplicemente:

ComponentFactory :: Instance(). CreateObject ("someStringAssociatedWithConcreteType", anDataCollection, aGamePointer);

Per creare oggetti, per registrarne uno basta usare ComponentFactory :: Instance(). Register() ;. C'è un grande capitolo sui dettagli nel libro Modern C++ Design.