Buona o cattiva pratica? Inizializzazione di oggetti in getter


167

Ho una strana abitudine sembra ... almeno secondo il mio collega. Abbiamo lavorato insieme su un piccolo progetto. Il modo in cui ho scritto le lezioni è (esempio semplificato):

[Serializable()]
public class Foo
{
    public Foo()
    { }

    private Bar _bar;

    public Bar Bar
    {
        get
        {
            if (_bar == null)
                _bar = new Bar();

            return _bar;
        }
        set { _bar = value; }
    }
}

Quindi, fondamentalmente, inizializzo qualsiasi campo quando viene chiamato un getter e il campo è ancora nullo. Ho pensato che questo avrebbe ridotto il sovraccarico non inizializzando nessuna proprietà che non fosse usata da nessuna parte.

ETA: Il motivo per cui l'ho fatto è che la mia classe ha diverse proprietà che restituiscono un'istanza di un'altra classe, che a sua volta ha anche proprietà con ancora più classi e così via. Chiamare il costruttore per la classe superiore significherebbe successivamente chiamare tutti i costruttori per tutte queste classi, quando non sono sempre tutte necessarie.

Vi sono obiezioni contro questa pratica, oltre alle preferenze personali?

AGGIORNAMENTO: ho preso in considerazione le diverse opinioni divergenti riguardo a questa domanda e seguirò la mia risposta accettata. Tuttavia, ora sono arrivato a una comprensione molto migliore del concetto e sono in grado di decidere quando usarlo e quando no.

Contro:

  • Problemi di sicurezza del thread
  • Non obbedire a una richiesta "setter" quando il valore passato è nullo
  • Micro-ottimizzazioni
  • La gestione delle eccezioni dovrebbe avvenire in un costruttore
  • È necessario controllare il codice null in class '

Professionisti:

  • Micro-ottimizzazioni
  • Le proprietà non restituiscono mai null
  • Ritardare o evitare di caricare oggetti "pesanti"

La maggior parte dei contro non è applicabile alla mia attuale libreria, tuttavia dovrei testare per vedere se le "micro-ottimizzazioni" stanno effettivamente ottimizzando qualcosa.

ULTIMO AGGIORNAMENTO:

Ok, ho cambiato la mia risposta. La mia domanda iniziale era se questa è una buona abitudine. E ora sono convinto che non lo sia. Forse lo userò ancora in alcune parti del mio codice attuale, ma non incondizionatamente e sicuramente non sempre. Quindi perderò la mia abitudine e ci penserò prima di usarlo. Grazie a tutti!


14
Questo è il modello a carico lento, non ti dà esattamente un meraviglioso vantaggio qui, ma è comunque una buona cosa.
Machinarius,

28
L'istanza pigra ha senso se si ha un impatto misurabile sulle prestazioni, o se quei membri vengono usati raramente e consumano una quantità esorbitante di memoria, o se ci vuole molto tempo per istanziarli e si desidera farlo solo su richiesta. In ogni caso, assicurati di tenere conto dei problemi di sicurezza del thread (il tuo codice attuale non è ) e considera l'utilizzo della classe Lazy <T> fornita .
Chris Sinclair,

10
Penso che questa domanda si adatti meglio a codereview.stackexchange.com
Eduardo Brites

7
@PLB non è un modello singleton.
Colin Mackay,

30
Sono sorpreso che nessuno abbia menzionato un bug grave con questo codice. Hai una proprietà pubblica che posso impostare dall'esterno. Se lo imposto su NULL, creerai sempre un nuovo oggetto e ignorerai l'accesso del mio setter. Questo potrebbe essere un bug molto serio. Per proprietà private, questo potrebbe essere okie. Personalmente, non mi piace fare ottimizzazioni così premature. Aggiunge complessità senza alcun vantaggio aggiuntivo.
SolutionYogi

Risposte:


170

Quello che hai qui è una - ingenua - implementazione di "inizializzazione pigra".

Risposta breve:

L'uso dell'inizializzazione lenta incondizionatamente non è una buona idea. Ha i suoi posti ma bisogna prendere in considerazione gli impatti che questa soluzione ha.

Contesto e spiegazione:

Implementazione concreta:
diamo prima un'occhiata al tuo esempio concreto e perché considero ingenua la sua implementazione:

  1. Infrange il Principio di Least Surprise (POLS) . Quando un valore viene assegnato a una proprietà, si prevede che questo valore venga restituito. Nella tua implementazione questo non è il caso di null:

    foo.Bar = null;
    Assert.Null(foo.Bar); // This will fail
  2. Presenta alcuni problemi di threading: Due chiamanti foo.Barsu thread diversi possono potenzialmente ottenere due diverse istanze di Bare una di esse sarà senza una connessione Fooall'istanza. Tutte le modifiche apportate a tale Baristanza vengono perse silenziosamente.
    Questo è un altro caso di violazione di POLS. Quando si accede solo al valore memorizzato di una proprietà, si prevede che sia thread-safe. Mentre potresti sostenere che la classe semplicemente non è thread-safe - incluso il getter della tua proprietà - dovresti documentarlo correttamente poiché non è il caso normale. Inoltre, l'introduzione di questo problema non è necessaria, come vedremo tra poco.

In generale:
è ora il momento di esaminare l'inizializzazione lazy in generale: l'
inizializzazione lazy viene solitamente utilizzata per ritardare la costruzione di oggetti che richiedono molto tempo per essere costruiti o che richiedono molta memoria una volta costruita completamente.
Questa è una ragione molto valida per usare l'inizializzazione lazy.

Tuttavia, tali proprietà normalmente non hanno setter, il che elimina il primo problema indicato sopra.
Inoltre, Lazy<T>per evitare il secondo problema verrebbe utilizzata un'implementazione sicura per i thread, come per esempio.

Anche quando si considerano questi due punti nell'implementazione di una proprietà pigra, i seguenti punti sono problemi generali di questo modello:

  1. La costruzione dell'oggetto potrebbe non avere esito positivo, causando un'eccezione da un getter di proprietà. Questa è ancora un'altra violazione di POLS e quindi dovrebbe essere evitata. Anche la sezione sulle proprietà nelle "Linee guida di progettazione per lo sviluppo di librerie di classi" afferma esplicitamente che i getter di proprietà non dovrebbero generare eccezioni:

    Evitare di generare eccezioni dai getter di proprietà.

    I getter di proprietà dovrebbero essere semplici operazioni senza alcuna condizione preliminare. Se un getter potrebbe generare un'eccezione, considerare la riprogettazione della proprietà come metodo.

  2. Le ottimizzazioni automatiche da parte del compilatore sono danneggiate, vale a dire inline e previsione del ramo. Si prega di consultare la risposta di Bill K per una spiegazione dettagliata.

La conclusione di questi punti è la seguente:
Per ogni singola proprietà implementata pigramente, dovresti aver considerato questi punti.
Ciò significa che si tratta di una decisione a seconda del caso e che non può essere presa come una buona pratica generale.

Questo modello ha il suo posto, ma non è una buona pratica generale quando si implementano le classi. Non dovrebbe essere usato incondizionatamente , a causa dei motivi sopra indicati.


In questa sezione voglio discutere alcuni dei punti che altri hanno avanzato come argomenti per l'utilizzo incondizionato dell'inizializzazione:

  1. Serializzazione:
    EricJ afferma in un commento:

    Un oggetto che può essere serializzato non avrà il suo contruttore invocato quando è deserializzato (dipende dal serializzatore, ma molti di quelli comuni si comportano così). Inserire il codice di inizializzazione nel costruttore significa che è necessario fornire supporto aggiuntivo per la deserializzazione. Questo modello evita quella codifica speciale.

    Esistono diversi problemi con questo argomento:

    1. La maggior parte degli oggetti non verrà mai serializzata. L'aggiunta di una sorta di supporto per esso quando non è necessario viola YAGNI .
    2. Quando una classe deve supportare la serializzazione, esistono modi per abilitarla senza una soluzione alternativa che non ha nulla a che fare con la serializzazione a prima vista.
  2. Micro-ottimizzazione: il tuo argomento principale è che vuoi costruire gli oggetti solo quando qualcuno li accede effettivamente. Quindi stai effettivamente parlando di ottimizzare l'utilizzo della memoria.
    Non sono d'accordo con questo argomento per i seguenti motivi:

    1. Nella maggior parte dei casi, alcuni altri oggetti in memoria non hanno alcun impatto su nulla. I computer moderni hanno abbastanza memoria. Senza un caso di problemi reali confermati da un profiler, si tratta di un'ottimizzazione pre-matura e ci sono buone ragioni contro di essa.
    2. Riconosco il fatto che a volte questo tipo di ottimizzazione è giustificata. Ma anche in questi casi l'inizializzazione lenta non sembra essere la soluzione corretta. Ci sono due ragioni per discuterne:

      1. L'inizializzazione lenta potenzialmente danneggia le prestazioni. Forse solo marginalmente, ma come ha mostrato la risposta di Bill, l'impatto è maggiore di quanto si possa pensare a prima vista. Quindi questo approccio sostanzialmente scambia le prestazioni contro la memoria.
      2. Se si dispone di un progetto in cui è un caso di utilizzo comune utilizzare solo parti della classe, ciò suggerisce un problema con il progetto stesso: la classe in questione molto probabilmente ha più di una responsabilità. La soluzione sarebbe quella di dividere la classe in diverse classi più mirate.

4
@JohnWillemse: questo è un problema della tua architettura. Dovresti riformattare le tue classi in modo che siano più piccole e più focalizzate. Non creare una classe per 5 cose / compiti diversi. Crea invece 5 classi.
Daniel Hilgarth,

26
@JohnWillemse Forse consideriamo questo un caso di ottimizzazione prematura allora. A meno che tu non abbia un collo di bottiglia delle prestazioni / memoria misurato , ti consiglio di non farlo poiché aumenta la complessità e introduce problemi di threading.
Chris Sinclair,

2
+1, questa non è una buona scelta di design per il 95% delle lezioni. L'inizializzazione lenta ha i suoi pro, ma non dovrebbe essere generalizzata per TUTTE le proprietà. Aggiunge complessità, difficoltà a leggere il codice, problemi di sicurezza del thread ... per nessuna ottimizzazione percepibile nel 99% dei casi. Inoltre, come ha affermato SolutionYogi come commento, il codice dell'OP è errato, il che dimostra che questo modello non è banale da implementare e dovrebbe essere evitato a meno che non sia effettivamente necessaria l'inizializzazione lenta.
Ken2k,

2
@DanielHilgarth Grazie per aver scritto fino in fondo (quasi) tutto sbagliato nell'uso incondizionato di questo modello. Ottimo lavoro!
Alex,

1
@DanielHilgarth Beh sì e no. La violazione è il problema qui quindi sì. Ma anche "no" perché POLS è rigorosamente il principio secondo cui probabilmente non sarai sorpreso dal codice . Se Foo non è stato esposto al di fuori del tuo programma, è un rischio che puoi prendere o meno. In questo caso, posso quasi garantire che alla fine rimarrai sorpreso , perché non controlli il modo in cui si accede alle proprietà. Il rischio è appena diventato un bug e la tua discussione sul nullcaso è diventata molto più forte. :-)
atlaste il

49

È una buona scelta di design. Altamente raccomandato per il codice della libreria o le classi principali.

Viene chiamato da una "inizializzazione lenta" o "inizializzazione ritardata" ed è generalmente considerata da tutti una buona scelta progettuale.

Innanzitutto, se si inizializza nella dichiarazione di variabili o costruttore a livello di classe, quando l'oggetto viene costruito, si ha il sovraccarico di creare una risorsa che non potrà mai essere utilizzata.

In secondo luogo, la risorsa viene creata solo se necessario.

Terzo, eviti che la spazzatura raccolga un oggetto che non è stato usato.

Infine, è più semplice gestire le eccezioni di inizializzazione che possono verificarsi nella proprietà rispetto alle eccezioni che si verificano durante l'inizializzazione delle variabili a livello di classe o del costruttore.

Ci sono delle eccezioni a questa regola.

Per quanto riguarda l'argomento delle prestazioni del controllo aggiuntivo per l'inizializzazione nella proprietà "get", è insignificante. L'inizializzazione e l'eliminazione di un oggetto è un risultato più significativo rispetto a un semplice controllo del puntatore null con un salto.

Linee guida di progettazione per lo sviluppo di librerie di classi all'indirizzo http://msdn.microsoft.com/en-US/library/vstudio/ms229042.aspx

per quanto riguarda Lazy<T>

La Lazy<T>classe generica è stata creata esattamente per quello che vuole il poster, vedi Inizializzazione pigra su http://msdn.microsoft.com/en-us/library/dd997286(v=vs.100).aspx . Se si dispone di versioni precedenti di .NET, è necessario utilizzare il modello di codice illustrato nella domanda. Questo modello di codice è diventato così comune che Microsoft ha ritenuto opportuno includere una classe nelle ultime librerie .NET per semplificare l'implementazione del modello. Inoltre, se l'implementazione richiede la sicurezza del thread, è necessario aggiungerla.

Tipi di dati primitivi e classi semplici

Obvioulsy, non userete l'inizializzazione lazy per un tipo di dati primitivo o un uso di classe semplice come List<string>.

Prima di commentare Lazy

Lazy<T> è stato introdotto in .NET 4.0, quindi per favore non aggiungere ancora un altro commento riguardo a questa classe.

Prima di commentare le microottimizzazioni

Quando si creano librerie, è necessario considerare tutte le ottimizzazioni. Ad esempio, nelle classi .NET vedrai array di bit utilizzati per le variabili di classe booleane in tutto il codice per ridurre il consumo di memoria e la frammentazione della memoria, solo per nominare due "micro-ottimizzazioni".

Per quanto riguarda le interfacce utente

Non si utilizzerà l'inizializzazione lazy per le classi utilizzate direttamente dall'interfaccia utente. La settimana scorsa ho trascorso la maggior parte della giornata rimuovendo il caricamento lento di otto raccolte utilizzate in un modello di visualizzazione per caselle combinate. Ho un LookupManagerche gestisce il caricamento e la memorizzazione nella cache delle raccolte necessarie per qualsiasi elemento dell'interfaccia utente.

"Setter"

Non ho mai usato una proprietà set ("setter") per nessuna proprietà caricata in modo pigro. Pertanto, non lo permetteresti mai foo.Bar = null;. Se è necessario impostare, Barvorrei creare un metodo chiamato SetBar(Bar value)e non utilizzare l'inizializzazione lazy

collezioni

Le proprietà della raccolta di classi vengono sempre inizializzate quando dichiarate perché non devono mai essere nulle.

Classi complesse

Consentitemi di ripeterlo in modo diverso, usate l'inizializzazione lazy per classi complesse. Che di solito sono classi mal progettate.

infine

Non ho mai detto di farlo per tutte le classi o in tutti i casi. È una cattiva abitudine.


6
Se puoi chiamare foo.Bar più volte in thread diversi senza alcuna impostazione di valore interveniente ma ottieni valori diversi, allora hai una classe scadente scadente.
Lie Ryan,

25
Penso che questa sia una cattiva regola empirica senza molte considerazioni. A meno che Bar non sia un esperto conoscitore di risorse, si tratta di un'inutile micro ottimizzazione. E nel caso in cui Bar disponga di un uso intensivo delle risorse, è disponibile il thread Lazy <T> sicuro all'interno di .net.
Andrew Hanlon,

10
"è più semplice gestire le eccezioni di inizializzazione che possono verificarsi nella proprietà rispetto alle eccezioni che si verificano durante l'inizializzazione delle variabili a livello di classe o del costruttore." - okay, è sciocco. Se un oggetto non può essere inizializzato per qualche motivo, voglio sapere al più presto. Cioè immediatamente quando è costruito. Ci sono buoni argomenti da fare per usare l'inizializzazione pigra, ma non credo sia una buona idea usarlo pervasivamente .
millimoose,

20
Sono davvero preoccupato che altri sviluppatori vedano questa risposta e pensano che questa sia davvero una buona pratica (oh ragazzo). Questa è una pessima pratica se la usi incondizionatamente. Oltre a ciò che è già stato detto, stai rendendo la vita di tutti molto più difficile (per gli sviluppatori client e per gli sviluppatori di manutenzione) con così poco guadagno (se non del tutto). Dovresti ascoltare i professionisti: Donald Knuth, nella serie The Art of Computer Programming, ha affermato che "l'ottimizzazione prematura è la radice di tutti i mali". Quello che stai facendo non è solo malvagio, è diabolico!
Alex

4
Ci sono così tanti indicatori che stai scegliendo la risposta sbagliata (e la decisione di programmazione sbagliata). Hai più svantaggi che pro nella tua lista. Hai più persone che lo garantiscono contro di esso. I membri più esperti di questo sito (@BillK e @DanielHilgarth) pubblicati in questa domanda sono contrari. Il tuo collega ti ha già detto che è sbagliato. Seriamente, è sbagliato! Se vedo uno degli sviluppatori della mia squadra (sono un capo squadra) fare questo, prenderà un timeout di 5 minuti e poi verrà insegnato perché non dovrebbe mai farlo.
Alex

17

Consideri l'implementazione di tale modello usando Lazy<T>?

Oltre alla facile creazione di oggetti caricati in modo pigro, si ottiene la sicurezza del thread durante l'inizializzazione dell'oggetto:

Come altri hanno già detto, carichi pigramente oggetti se sono veramente ricchi di risorse o ci vuole del tempo per caricarli durante i tempi di costruzione degli oggetti.


Grazie, lo capisco ora e lo esaminerò sicuramente Lazy<T>ora, e mi asterrò dall'usare come facevo sempre.
John Willemse,

1
Non hai la sicurezza del filo magico ... devi ancora pensarci. Da MSDN:Making the Lazy<T> object thread safe does not protect the lazily initialized object. If multiple threads can access the lazily initialized object, you must make its properties and methods safe for multithreaded access.
Eric J.

@EricJ. Certo certo. Quando si inizializza l'oggetto, si ottiene la sicurezza del thread, ma in seguito è necessario gestire la sincronizzazione come qualsiasi altro oggetto.
Matías Fidemraizer,

9

Penso che dipenda da cosa stai inizializzando. Probabilmente non lo farei per un elenco in quanto il costo di costruzione è piuttosto piccolo, quindi può andare nel costruttore. Ma se fosse un elenco precompilato, probabilmente non lo farei finché non fosse necessario per la prima volta.

Fondamentalmente, se il costo di costruzione supera il costo di fare un controllo condizionale su ogni accesso, allora pigro crearlo. In caso contrario, fallo nel costruttore.


Grazie! Questo ha senso.
John Willemse,

9

Il rovescio della medaglia che posso vedere è che se vuoi chiedere se Bars è nullo, non lo sarebbe mai e creeresti l'elenco lì.


Non credo sia un aspetto negativo.
Peter Porfy,

Perché è un aspetto negativo? Basta controllare con qualsiasi invece contro null. if (! Foo.Bars.Any ())
s.meijer

6
@PeterPorfy: viola POLS . Ci metti nulldentro, ma non riaverlo indietro. Normalmente, si presume che si ottenga lo stesso valore restituito in una proprietà.
Daniel Hilgarth,

@DanielHilgarth Grazie ancora. Questo è un argomento molto valido che non avevo mai considerato prima.
John Willemse,

6
@AMissico: non è un concetto inventato. Allo stesso modo in cui si prevede che premendo un pulsante accanto a una porta d'ingresso suona un campanello, ci si aspetta che le cose che sembrano proprietà si comportino come proprietà. Aprire una botola sotto i piedi è un comportamento sorprendente, soprattutto se il pulsante non è etichettato come tale.
Bryan Boettcher,

8

L'istanza / inizializzazione lenta è un modello perfettamente praticabile. Tieni presente, tuttavia, che, come regola generale, i consumatori della tua API non si aspettano che getter e setter impieghino tempo riconoscibile dal POV dell'utente finale (o falliscano).


1
Sono d'accordo e ho leggermente modificato la mia domanda. Mi aspetto che una catena completa di costruttori sottostanti impieghi più tempo rispetto all'istanza delle classi solo quando sono necessarie.
John Willemse,

8

Stavo solo per inserire un commento sulla risposta di Daniel, ma onestamente non penso che vada abbastanza lontano.

Anche se questo è un ottimo modello da utilizzare in determinate situazioni (ad esempio, quando l'oggetto viene inizializzato dal database), è un'abitudine ORRIBILE per entrare.

Una delle cose migliori di un oggetto è che offre un ambiente sicuro e affidabile. Il caso migliore è se crei il maggior numero possibile di campi "Final", riempiendoli tutti con il costruttore. Questo rende la tua classe abbastanza a prova di proiettile. Consentire la modifica dei campi tramite setter è un po 'meno, ma non è terribile. Per esempio:

classe SafeClass
{
    String name = "";
    Età intera = 0;

    public void setName (String newName)
    {
        assert (newName! = null)
        name = newName;
    } // segui questo schema per età
    ...
    public String toString () {
        String s = "La classe sicura ha nome:" + nome + "ed età:" + età
    }
}

Con il tuo modello, il metodo toString sarebbe simile al seguente:

    if (nome == null)
        getta nuovo IllegalStateException ("SafeClass è entrato in uno stato illegale! il nome è null")
    if (età == null)
        gettare nuovo IllegalStateException ("SafeClass è entrato in uno stato illegale! l'età è nulla")

    public String toString () {
        String s = "La classe sicura ha nome:" + nome + "ed età:" + età
    }

Non solo, ma hai bisogno di controlli null ovunque tu possa eventualmente usare quell'oggetto nella tua classe (fuori dalla tua classe è sicuro a causa del controllo null nel getter, ma dovresti usare principalmente i membri della tua classe all'interno della classe)

Anche la tua classe è perennemente in uno stato incerto, ad esempio se decidi di rendere quella classe una classe in letargo aggiungendo alcune annotazioni, come faresti?

Se si prende una decisione basata su una micro-optomizzazione senza requisiti e test, è quasi sicuramente una decisione sbagliata. In effetti, c'è davvero una buona possibilità che il tuo modello stia effettivamente rallentando il sistema anche nelle circostanze più ideali perché l'istruzione if può causare un errore di previsione del ramo sulla CPU che rallenterà molte molte molte più volte rispetto a assegnare semplicemente un valore nel costruttore a meno che l'oggetto che si sta creando non sia abbastanza complesso o provenga da un'origine dati remota.

Per un esempio del problema della previsione del brance (che stai riscontrando ripetutamente, quasi una volta sola), vedi la prima risposta a questa fantastica domanda: Perché è più veloce elaborare un array ordinato rispetto a un array non ordinato?


Grazie per il tuo contributo. Nel mio caso, nessuna delle classi ha alcun metodo che potrebbe essere necessario verificare la presenza di null, quindi non è un problema. Prenderò in considerazione le tue altre obiezioni.
John Willemse,

Non lo capisco davvero. Implica che non stai usando i tuoi membri nelle classi in cui sono memorizzati - che stai semplicemente usando le classi come strutture di dati. In tal caso, potresti voler leggere javaworld.com/javaworld/jw-01-2004/jw-0102-toolbox.html che contiene una buona descrizione di come migliorare il tuo codice evitando di manipolare esternamente lo stato dell'oggetto. Se le stai manipolando internamente, come puoi farlo senza controllare ripetutamente tutto ripetutamente?
Bill K,

Parti di questa risposta sono buone, ma parti sembrano inventate. Normalmente quando si utilizza questo modello, toString()chiamerebbe getName(), non utilizzare namedirettamente.
Izkata,

@BillK Sì, le classi sono un'enorme struttura di dati. Tutto il lavoro viene svolto in classi statiche. Controllerò l'articolo al link. Grazie!
John Willemse,

1
@izkata In realtà all'interno di una classe sembra essere una confusione sul tempo che usi un getter o no, la maggior parte dei posti in cui ho lavorato usavano direttamente il membro. A parte questo, se si utilizza sempre un getter il metodo if () è ancora più dannoso perché gli errori di previsione del ramo si verificheranno più spesso E a causa della ramificazione il runtime avrà probabilmente più problemi a inserire il getter. È tutto controverso, tuttavia, con la rivelazione di John che sono strutture di dati e classi statiche, questa è la cosa di cui mi preoccuperei maggiormente.
Bill K,

4

Vorrei solo aggiungere un altro punto a molti punti positivi fatti da altri ...

Il debugger valuterà ( per impostazione predefinita ) le proprietà quando passa attraverso il codice, il che potrebbe potenzialmente creare un'istanza Barprima di quanto accadrebbe normalmente eseguendo semplicemente il codice. In altre parole, il semplice atto di debug sta cambiando l'esecuzione del programma.

Questo può o meno essere un problema (a seconda degli effetti collaterali), ma è qualcosa di cui essere consapevoli.


2

Sei sicuro che Foo dovrebbe istanziare qualcosa?

A me sembra puzzolente (anche se non necessariamente sbagliato ) lasciare a Foo un'istanza per nulla. A meno che lo scopo esplicito di Foo non sia quello di essere una fabbrica, non dovrebbe istanziare i propri collaboratori, ma piuttosto farli iniettare nel suo costruttore .

Se comunque lo scopo di Foo di essere è creare istanze di tipo Bar, allora non vedo nulla di male nel farlo pigramente.


4
@BenjaminGruenbaum No, non proprio. E rispettosamente, anche se lo fosse, che punto stavi cercando di chiarire?
KaptajnKold,
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.