2009-02-07 10 views
7

Ho iniziato con la stesura di una domanda: "Qual è il modo migliore per eseguire il test delle unità su un costruttore (ad esempio, __construct() in PHP5)", ma quando ho letto le domande correlate, ho visto diversi commenti che sembravano suggerire le variabili membro di impostazione o l'esecuzione di operazioni complicate nel costruttore sono no-nos.Quali sono le cose migliori da fare in un costruttore?

Il costruttore per la classe in questione qui prende un param, esegue alcune operazioni su di esso (assicurandosi che passi un test di sniffing e lo trasformi se necessario), quindi lo memorizza in una variabile membro.

ho pensato i benefici di farlo in questo modo sono stati:

1) che il codice cliente sarebbe sempre certi di avere un valore per questa variabile membro ogni volta che un oggetto di questa classe viene creata un'istanza, e

2) consente di risparmiare un passo nel codice client (uno dei quali potrebbe in teoria essere perse), per esempio,

$Thing = new Thing; 
$Thing->initialize($var); 

quando potevamo semplicemente farlo

$Thing = new Thing($var); 

e fatelo.

È un no-no? Se è così, perché?

risposta

8

Questo viene in su un bel po 'nelle discussioni C++, e la conclusione generale Sono venuto a c'è stato questo:

Se un oggetto non acquisisce alcun risorse esterne, i membri devono essere inizializzati in il costruttore. Ciò implica fare tutto il lavoro nel costruttore.

  • (x, y) di coordinate (o realmente qualsiasi altra struttura che è solo una tupla glorificato)
  • US tabella sigla dello stato di ricerca

Se un oggetto acquisisce risorse che essa può controllare, essi possono essere assegnatinel costruttore:

  • file aperto descrittore
  • memoria allocata
  • maniglia/puntatore in una libreria esterna

Se l'oggetto acquisisce risorse che non può controllare interamente, essi deve essere allocatofuori del costruttore:

    connessione TCP
  • connessione
  • DB
  • siamo ak reference

Ci sono sempre delle eccezioni, ma questo copre la maggior parte dei casi.

-1

Non dovresti mettere le cose in un costruttore che dovrebbe essere eseguito una volta sola quando viene creata la classe.

Per spiegare.

Se avessi una classe di database. Qualora il costruttore è la connessione al database Così

$db = new dbclass; 

e ora mi sono collegato al database.

Quindi abbiamo una classe che utilizza alcuni metodi all'interno della classe del database.

class users extends dbclass 
{ 
    // some methods 
} 

$users = new users 
// by doing this, we have called the dbclass's constructor again 
+0

In questo caso preferirei passare in un'istanza di dbClass, o usarlo come un Singleton, piuttosto che estendo la classe stessa. – Ross

+0

Sì, ci sono modi per aggirarlo, ma questo è solo un esempio. –

+0

Se non si desidera tale funzionalità, è possibile sovrascrivere il costruttore nell'oggetto utenti. Inoltre alcune lingue, come C#, non ereditano affatto il costruttore. –

6

Costruttori sono per inizializzare l'oggetto, così

$Thing = new Thing($var); 

è perfettamente accettabile.

16

La mia regola generale è che un oggetto dovrebbe essere pronto per l'uso dopo che il costruttore ha finito. Ma ci sono spesso un numero di opzioni che possono essere modificate in seguito.

La mia lista di cose da fare e donts:

  • Costruttori dovrebbe impostare le opzioni di base per l'oggetto.
  • Dovrebbero forse creare istanze di oggetti helper.
  • Essi dovrebbero non risorse aqquire (file, socket, ...), a meno che l'oggetto non sia chiaramente un involucro attorno ad alcune risorse.

Naturalmente, nessuna regola senza eccezioni. L'importante è che tu pensi al tuo design e alle tue scelte. Rendi naturale l'utilizzo degli oggetti, e questo include la segnalazione degli errori.

2

Per migliorare la testabilità di una classe, in genere è consigliabile mantenere il costruttore il più semplice possibile e chiedergli solo le cose di cui ha assolutamente bisogno. C'è un eccellente presentation disponibile su YouTube come parte della serie "Colloqui di codice pulito" di Google che spiega in dettaglio questo.

+1

+1 Sono d'accordo. Abbiamo tonnellate di codice in cui i costruttori in realtà aprono i file direttamente per leggere alcune informazioni di configurazione. In qualche modo, rende questi oggetti comodi da usare ma scrivere test di unità sarebbe impossibile. Per fortuna non abbiamo test unitari ;-) –

0

Dipende dal tipo di sistema che si sta tentando di progettare, ma in generale credo che i costruttori siano utilizzati al meglio solo inizializzando lo "stato" dell'oggetto, ma non eseguano alcuna transizione di stato. Meglio per aver impostato le impostazioni predefinite.

Poi scrivo un metodo "handle" nei miei oggetti per gestire cose come input dell'utente, chiamate database, eccezioni, regole di confronto, qualsiasi cosa. L'idea è che questo gestirà lo stato in cui si trova l'oggetto in base a forze esterne (input dell'utente, tempo, ecc.) Fondamentalmente, tutte le cose che possono cambiare lo stato dell'oggetto e richiedono un'azione aggiuntiva vengono scoperte e rappresentate nel oggetto.

Infine, ho inserito un metodo di rendering nella classe per mostrare all'utente qualcosa di significativo. Questo rappresenta solo lo stato dell'oggetto per l'utente (qualunque cosa possa essere.)

__construct ($ argomenti)
maniglia()
render (Exception $ ex = null)

0

Il __construct metodo magico è bene usare. La ragione per cui si inizializza in molti framework e applicazioni è perché quell'oggetto è programmato su un'interfaccia o sta tentando di implementare un modello singleton/getInstance.

Questi oggetti vengono generalmente inseriti nel contesto o in un controller e quindi dispongono della funzionalità di interfaccia comune richiamata da altri oggetti di livello superiore.

1

Si dovrebbe evitare di fare il cliente deve chiamare

$thing->initialize($var) 

Quel genere di cose appartiene assolutamente nel costruttore. È solo ostile al programmatore del client far sì che lo chiamino. C'è una scuola di pensiero (leggermente controversa) che dice che dovresti scrivere classi in modo che gli oggetti siano mai in uno stato non valido - e 'non inizializzato' è uno stato non valido.

Tuttavia, per motivi di testabilità e prestazioni, a volte è opportuno posticipare alcune inizializzazioni fino a più tardi nella vita dell'oggetto. In casi come questi, la valutazione pigra è la soluzione.

Scuse per mettere la sintassi di Java in una risposta Python, ma:

// Constructor 
public MyObject(MyType initVar) { 
     this.initVar = initVar; 
} 

private void lazyInitialize() { 
    if(initialized) { 
     return 
    } 
    // initialization code goes here, uses initVar 
} 

public SomeType doSomething(SomeOtherType x) { 
    lazyInitialize(); 
    // doing something code goes here 
} 

è possibile segmentare l'inizializzazione differita in modo che solo le parti che ne hanno bisogno vengono inizializzati. È comune, ad esempio, fare questo nei getter, solo per ciò che influenza il valore che si sta ottenendo.

4

Il lavoro di un costruttore è quello di stabilire un'istanza invariants.

Tutto ciò che non contribuisce a ciò è meglio tenere fuori dal costruttore.

0

Se $ var è assolutamente necessario per $ Thing di lavorare, allora è una DO