2009-08-16 3 views
29

Ho un sacco di fabbriche (astratte) e di solito vengono implementate come singleton.Fabbriche "Singleton", ok o cattive?

Solitamente per la comodità di non dover passare attraverso strati che non hanno davvero alcun rapporto con l'utilizzo o la conoscenza di queste fabbriche.

maggior parte delle volte ho solo bisogno di prendere una decisione in fase di avvio del quale implementazione fabbrica il resto del programma in codice, forse attraverso qualche configurazione

sembra per esempio come

abstract class ColumnCalculationFactory { 
    private static ColumnCalculationFactory factory; 

public static void SetFactory(ColumnCalculationFactory f) { 
     factory = f; 
    } 

    public static void Factory() { 
     return factory; 
    } 

public IPercentCalculation CreatePercentCalculation(); 
public IAverageCalculation CreateAverageCalculation(); 
    .... 

} 

Someting fare odore di questo, solo che non sono sicuro di quello che - è forse più un disuised globale di un Singleton. Non è che ci siano davvero a essere una sola fabbrica a creare ColumnCalculations, anche se i miei programmi non ne hanno bisogno.

È considerato utile? Dovrei piuttosto inserirli in alcune classi (semi) globali di AppContext? Qualcos'altro (non sono ancora pronto per passare ad un contenitore IoC più grande, o ancora spring.net)?

risposta

11

Dipende molto da cosa stai facendo e dalla portata della tua applicazione. Se è solo un'app abbastanza piccola e non andrà mai oltre questo, allora il tuo approccio attuale potrebbe andar bene. Non esiste una pratica "migliore" universale per queste cose. Anche se non consiglierei l'uso di singleton per qualcosa di diverso dai metodi foglia stateless e/o le chiamate unidirezionali (ad esempio la registrazione), escludendolo "solo perché" è un singleton non è necessariamente la cosa giusta da fare.

Per qualcosa di diverso dal codice banale o prototipo, personalmente desidero utilizzare esplicitamente l'inversione del controllo con l'iniezione del costruttore, poiché significa che tutte le dipendenze sono state prese in considerazione e non si ottengono "sorprese". Il compilatore non ti permetterà di istanziare A senza B e B senza C. Singletons interrompe immediatamente queste relazioni - puoi istanziare A senza B e B senza C. Quando la chiamata da A a B avviene, otterrai un riferimento nullo eccezione.

Ciò è particolarmente fastidioso durante il test, poiché è necessario eseguire iterativamente all'indietro tramite errori di runtime. Quando esegui il test del codice, stai utilizzando l'API proprio come farebbe un altro codificatore, quindi è indicativo dei problemi di progettazione con questo approccio. L'iniezione del costruttore garantisce che questo non possa mai accadere - tutte le dipendenze sono dichiarate in anticipo. Il rovescio della medaglia con l'iniezione del costruttore è che la configurazione del tuo oggetto grafico è più complicata. Questo è mitigato attraverso l'uso di un contenitore IoC.

Immagino che quello che sto cercando di dire è che se sei arrivato al punto in cui stai prendendo in considerazione l'utilizzo di qualche tipo di oggetto di contesto e di un modello di registro, puoi anche dare un'occhiata ai contenitori IoC. Passare allo sforzo di rotolare la tua versione di mutt è probabilmente una perdita di tempo quando puoi usare un prodotto consolidato e gratuito come Autofac.

3

È considerata la migliore prassi?

Non vedo alcun problema: lei ha detto "i miei programmi non hanno bisogno di più", quindi il vostro attuare qualcosa di più flessibile/astratto potrebbe essere un caso di You Ain't Gonna Need It.

6

No, perché quello che stai facendo qui sta creando uno stato globale. Ci sono tutti i tipi di problemi con lo stato globale - tra cui, in primo luogo, una funzione dipende in modo piuttosto invisibile dal comportamento di altre funzioni. Se una funzione chiama un'altra funzione che dimentica di archiviare e ripristinare lo factory prima che finisca, allora hai un problema perché non puoi nemmeno recuperare il vecchio valore se non lo hai memorizzato da qualche parte. E devi aggiungere del codice per farlo (a giudicare dal tuo codice, direi che sei in una lingua con finally, che lascia ancora più spazio per errori). Inoltre, se si finisce con il codice che deve passare rapidamente tra due fabbriche per due sottooggetti, è necessario scrivere una chiamata al metodo in ogni punto: non è possibile memorizzare lo stato in ciascun sottooggetto (beh, è ​​possibile, ma in questo caso sconfiggi lo scopo dello stato globale [che, ammettiamolo, non è molto]).

Probabilmente ha più senso memorizzare il tipo di fabbrica come membro e passarlo al costruttore di nuovi oggetti che ne hanno bisogno (o crearne uno nuovo secondo necessità, ecc.). Ti dà anche un controllo migliore: puoi garantire che tutti gli oggetti costruiti dall'oggetto A passino attraverso lo stesso stabilimento o che tu offra metodi per scambiare le fabbriche.

+0

anche se dato al setFactory pubblico esposto non c'è alcuna garanzia che qualcuno non possa scambiare altro pasticcio con la fabbrica, di solito mi sembra di impostare la fabbrica solo all'avvio, sulla base di una iniziale desicion o configurazione. – leeeroy

+0

poi cadi preda dello svantaggio dei singleton ... sono single. – coppro

4

Vorrei sconsigliarlo, perché rende molto difficile scrivere test di unità decenti per il codice che chiama queste fabbriche.

Misko Hevery ha uno nice article sul suo blog.

+0

Con il codice sopra puoi facilmente prendere in giro una fabbrica. – nos

+0

Certo, ma sapresti istantaneamente quali devi deridere, in un'app non banale in cui non conosci a memoria le dipendenze delle classi? Di nuovo, mi riferisco all'articolo di Misko. – jqno

+0

Misko ha anche alcuni video eccellenti su youtube su questo argomento, http://www.youtube.com/watch?v=acjvKJiOvXw – Despertar

11

Avere pochi singletons è piuttosto tipico e di solito non è problematico: un sacco di singleton porta a qualche codice irritante.

Abbiamo appena attraversato una situazione in cui dovevamo testare le nostre classi pesantemente singleton. Il problema è che quando si prova la classe b, e si ottiene la classe c (un singleton), non si ha modo di deridere la classe c (Almeno EasyMock non ci permetterebbe di sostituire il metodo statico di fabbrica di una classe singleton.

Una semplice soluzione consiste nell'avere "setter" per tutti i tuoi singleton a scopo di test. Non proprio raccomandato.

Un'altra cosa che abbiamo provato era quella di avere una singola classe che contenesse tutti i singleton: un registro. Fare questo sta diventando piuttosto vicino all'iniezione di dipendenza che è quello che dovresti quasi sicuramente usare.

Oltre ai test, ho imparato molto tempo fa quando non ci sono mai e poi mai più di una istanza di un dato oggetto; nel prossimo giro ne vogliono spesso due, il che rende i singleton MOLTO meglio delle classi statiche - almeno è possibile aggiungere un parametro al getter di un singleton e restituire un secondo senza troppo refactoring (che sta facendo di nuovo ciò che fa DI).

In ogni caso, guarda DI, potresti essere davvero felice.

+0

Concordato. Un'altra conseguenza dei singleton è la "perdita" dei valori impostati durante i test unitari. Abbiamo dovuto generare biforcazioni JVM per ogni test, dal momento che un sottotesto singleton o appena utilizzato da un test era nello stato A, ea valle doveva essere nello stato B. – Trenton

+0

Il problema con TDD esteso è che tu spesso complicano il codice e lo rendono più incline agli errori per l'utilizzo di strumenti di test. Credo fermamente che se devi modificare l'architettura per il gusto di Mocking, stai sfruttando eccessivamente Mocking e dovresti invece utilizzare una configurazione presunta di Factory/Singleton. La copertura del 100% del codice è folle e causerà più problemi e complessità di quanto non risolverà. – user3225313

+0

Sono d'accordo su TDD, ma non è l'unica ragione per usare DI. Inoltre, il codice TESTABLE è in genere migliore del codice non testabile, indipendentemente dall'esistenza effettiva dei test. Avere DI invece di un singleton non è mai male e può essere davvero buono nel caso in cui in realtà si potrebbe voler testarlo, anche se non si dispone di una copertura di prova del 100%. –

1

La cattiva forma di Singleton è quello che implementa l'equivalente del tuo metodo di fabbrica con:

return new CalculationFactory(); 

Questo è molto più difficile da stub, finta o avvolgere.

La versione che restituisce un oggetto creato altrove è molto meglio, anche se, come quasi tutto, può essere utilizzata in modo improprio o sovrautilizzata.