2013-04-09 10 views
19

Ho risposto a una domanda (link) che ho usato una creazione del nuovo oggetto in un'altra classe costruttore, qui l'esempio:Perché non "creare un'istanza di un nuovo oggetto all'interno del costruttore di oggetti"?

class Person { 
    public $mother_language; 

    function __construct(){ // just to initialize $mother_language 
    $this->mother_language = new Language('English'); 
} 

E ho avuto il commento da utente 'Matija' (his profile) e lui ha scritto: Non dovresti mai istanziare un nuovo oggetto all'interno del consturctor dell'oggetto, le dipendenze dovrebbero essere spinte dall'esterno, quindi chiunque usi questa classe sa da cosa dipende questa classe!

In generale, sono d'accordo con questo, e comprendo il suo punto di vista.

Tuttavia, ho usato per fare questo in questo modo molto spesso, ad esempio:

  • come le proprietà private altre classi mi danno la funzionalità che posso risolvere non duplicare il codice, ad esempio, posso creare una lista (classe che implementa ArrayAccess interfaccia) di oggetti), e questa classe verrebbe utilizzato in un'altra classe, che ha una tale elenco di oggetti,
  • alcune classi usare per esempio DateTime oggetti,
  • se include (o autocaricamento) dipendente classe, non si dovrebbe avere problemi con err ors,
  • perché gli oggetti dipendenti possono essere molto numerose, passando tutti al costruttore della classe può essere molto lungo e non è chiaro, ad esempio

    $color = new TColor('red'); // do we really need these lines? 
    $vin_number = new TVinNumber('xxx'); 
    $production_date = new TDate(...); 
    ... 
    $my_car = new TCar($color, $vin_number, $production_date, ...............); 
    
  • come mi è stato "nato" in Pascal, poi a Delphi, ho alcune abitudini da lì. E in Delphi (e FreePascal come suo concorrente) questa pratica è molto spesso. Ad esempio, esiste una classe TStrings che gestisce l'array di stringhe e per archiviarle non utilizza array s ma un'altra classe, TList, che fornisce alcuni metodi utili, mentre TStrings è solo una sorta di interfaccia. L'oggetto TList è dichiarato privato e non ha accesso dall'esterno, ma i getter e setter dello TStrings.

  • (non importante, ma qualche ragione) di solito sono quello che usa le mie lezioni.

Per favore spiegami, è davvero importante evitare di creare oggetti nei costruttori?

Ho letto this discussion ma ho ancora una mente poco chiara.

+0

@ Matia Leggo, grazie. Volevo dire che capisco cosa intendi ma non posso essere d'accordo nel permettere l'accesso alle proprietà della classe dall'esterno a chiunque altro. Se fornisco un corso a qualcuno, penso che dovrebbe ottenere un dispositivo funzionante, che possa costruire e ottenere tutto ciò di cui ha bisogno. Non voglio fargli creare molti oggetti aggiuntivi che il suo scopo potrebbe non essere chiaro o voglio mantenerlo segreto, ecco perché. E non voglio che lui gestisca questo oggetto di proprietà fuori dalla mia classe, questo è il mio oggetto e stare lontano da esso. – Voitcus

+0

@Voitcus Ecco perché abbiamo contenitori di iniezione delle dipendenze, che possono costruire oggetti per noi, con tutte le loro dipendenze. Quindi l'utente del tuo sistema non costruirà tutti gli oggetti, dirà semplicemente: $ container-> getService ("Person"); E il contenitore utilizzerà Dependency Injection per costruire oggetti Person con tutte le dipendenze. Ho scritto un bel piccolo contenitore per l'iniezione di dipendenze per il mio MVC, puoi verificarlo qui: https://github.com/matijabozic/php_services – Matija

+0

@Matija Si prega di postarlo come risposta e quindi potrei accettarlo – Voitcus

risposta

12

Sì, lo è davvero. Allora sei chiaro su ciò di cui l'oggetto ha bisogno per essere costruito. Un gran numero di oggetti dipendenti passati è un odore di codice che forse la tua classe sta facendo troppo e dovrebbe essere suddiviso in più classi più piccole.

Il vantaggio principale del passaggio in oggetti dipendenti viene fornito se si desidera testare il codice. Nel tuo esempio, non posso usare una classe di lingua falsa. Devo usare la classe reale per testare Persona. Ora non riesco a controllare come si comporta Language per assicurarsi che la Persona funzioni correttamente.

Questo post aiuta a spiegare perché questa è una cosa negativa e i potenziali problemi che essa causa. http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

UPDATE

test a parte, passando gli oggetti dipendenti rende anche il codice più esplicito, flessibile ed estensibile. Per citare il post del blog a cui mi collego:

Quando la costruzione del collaboratore è mista all'inizializzazione, suggerisce che esiste un solo modo per configurare la classe, che chiude le opportunità di riutilizzo che altrimenti potrebbero essere disponibili.

Nel tuo esempio, puoi creare solo persone che hanno "inglese" come lingua. Ma che dire quando vuoi creare qualcuno che parli "francese". Non posso definirlo.

Come per la creazione degli oggetti e il loro passaggio, questo è l'intero scopo del modello Factoryhttp://www.oodesign.com/factory-pattern.html. Creerebbe le dipendenze e le inietterebbe per te. Quindi potresti chiederlo per l'oggetto che sarebbe inizializzato nel modo che desideri. L'oggetto Persona non dovrebbe decidere quale deve essere.

+0

Non prendi in giro gli oggetti facendo un orribile casino del tuo codice. Li prendi in giro scambiando le loro definizioni di tipo per le definizioni _alternative_. –

+1

Ma se gli oggetti sono istanziati nel costruttore, non è possibile scambiarli affatto. Devi usare ciò che è specificato lì. – Schleis

+0

@Schleis: che assurdità totale. Stai creando un _object_ dal costruttore, ma non è questo il luogo in cui crei il _type_ dell'oggetto. Quindi il costruttore include 'new T' - great! Buona! Ora, altrove, cambia ciò che si trova tra 'class T {' e '}' per soddisfare le tue esigenze di test. Questo è chiamato _mocking_. –

7

Il commento originale mi sembra sciocco.

Non c'è motivo di avere paura delle classi che gestiscono le risorse. In effetti, l'incapsulamento fornito dalle classi è perfecty apt per questa attività.

Se si limita a spingere le risorse solo dall'esterno, cosa gestirà tali risorse? Codice procedurale spaghetti? Yikes!

Cerca di evitare di credere a tutto ciò che leggi su Internet.

+3

+1 Oh guarda - sanità mentale! –

+3

I la risposta alla domanda nella discussione che ha menzionato ha un punto reale. Potrebbe esserci qualche classe che richiede davvero tempo quando la installi. se poi la tua classe quasi mai o solo in qualche raro caso usa quella classe, allora perché instenderla nel costruttore? sarebbe solo uno spreco di prestazioni! – ITroubs

+3

@ITroubs: come puoi parlare delle prestazioni? Hai profilato cose? Uno dovrebbe concentrarsi * prima * sulla chiarezza del codice, non sulle prestazioni. – ereOn

3

Penso che dipenda dalla situazione.Ad esempio, se si esamina Doctrine 2, si consiglia di impostare sempre i valori nulli e impostare i valori nel costruttore. Questo perché Doctrine 2 salta l'istanza della classe quando viene recuperata dal database.

È perfettamente possibile creare un'istanza di un nuovo DateTime o ArrayCollection, è possibile aggiungere anche oggetti relazionali predefiniti. Personalmente mi piace Iniettare le tue dipendenze, il tuo codice è davvero facile da testare e modificare con poco sforzo, ma alcune cose hanno solo più senso creare un'istanza nel tuo costruttore.

4

Questo è più un problema di testabilità che una cosa giusta/sbagliata, se crei un oggetto sul tuo costruttore non sarai mai in grado di testare la tua classe in modo indipendente.

Se la classe ha bisogno di molte iniezioni sul costruttore, è sempre possibile utilizzare un metodo di fabbrica per iniettare tali oggetti.

In questo modo, sarai libero di prendere in giro qualcuno di quegli oggetti iniettati per testare davvero la tua classe.

<?php 

class Person { 
    public $mother_language; 

    // We ask for a known interface we don't mind the implementation 
    function __construct(Language_Interface $mother_language) 
    { 
    $this->mother_language = $mother_language 
    } 

    static function factory() 
    { 
    return new Person(new Language('English')); 
    } 
} 

$person = Person::factory(); 
$person = new Person(new Language('Spanish'); // Here you are free to inject your mocked object implementing the Language_Interface 
+1

'se crei un oggetto sul tuo costruttore non sarai mai in grado di testare la tua classe in modo indipendente. Che assurdità totale. Puoi scrivere 'new T()' all'interno del costruttore e ancora prendere in giro 'T', scambiando la sua definizione. Certo, questo è _far_ più facile in C++ quando si ha il controllo preciso delle unità di traduzione collegate al proprio eseguibile, ma non è tanto meno il caso qui. E certamente fare una regola generale - come sembra suggerire la domanda - che tu non debba mai creare oggetti in un costruttore è ridicolo. –

+0

Non sto dicendo che non dovresti mai farlo. Dico solo che questo test di difficoltà, potrei essere troppo categorico in quel "mai", ma sarà sempre più facile con le iniezioni. A proposito, non penso nemmeno che questo dovrebbe essere sempre fatto in questo modo ... – arraintxo

+0

Se dovessi avere una fabbrica, la chiamerei PersonFactory e la metterò in una classe diversa. Non mi piace avere metodi statici. –

2

La maggior parte di queste risposte sembra ragionevole. Ma faccio sempre quello che fai. Se lo sto testando, allora ho o un setter o semplicemente l'ho impostato su qualcosa (visto che "mother_language" è pubblico).

Ma personalmente penso che dipenda da ciò che state facendo. Dal codice che sto vedendo pubblicato da altri, se la tua classe crea un'istanza di oggetti, il costruttore dovrebbe sempre prendere alcuni parametri.

E se volessi creare un oggetto che istanzia un altro oggetto dove quell'oggetto interno istanzia un altro e così via? Sembra che avrò molto nelle mie mani.

Per me, questa affermazione sembra che stia dicendo "non usare CamelCase, usa un under_score". Penso che dovresti farlo in qualsiasi modo funzioni per te. Dopotutto, hai il tuo modo di testare, vero?

1

Se si desidera impostare un valore predefinito, ma migliorare la flessibilità/testabilità, perché non farlo in questo modo?

class Person 
{ 
    protected $mother_language; 

    function __construct(Language $language = null) 
    { 
     $this->mother_language = $language ?: new Language('English'); 
    } 
} 

Alcune persone ancora può non piacere, ma è è un miglioramento.

Problemi correlati