2009-04-15 3 views

risposta

18

È uno schema abbastanza utilizzabile se ci sono molte cose che devono essere impostate su un oggetto.

class Foo 
{ 
     int x, y, z; 
public: 
     Foo &SetX(int x_) { x = x_; return *this; } 
     Foo &SetY(int y_) { y = y_; return *this; } 
     Foo &SetZ(int z_) { z = z_; return *this; } 
}; 

int main() 
{ 
     Foo foo; 
     foo.SetX(1).SetY(2).SetZ(3); 
} 

Questo modello sostituisce un costruttore che prende tre interi:

int main() 
{ 
     Foo foo(1, 2, 3); // Less self-explanatory than the above version. 
} 

E 'utile se si dispone di una serie di valori che non hanno bisogno sempre da impostare.

Per riferimento, un esempio più completo di questo tipo di tecnica è indicato come "Named Parameter Idiom" nelle Domande frequenti su C++ Lite.

Ovviamente, se si utilizza questo parametro per i parametri denominati, è possibile dare un'occhiata a boost::parameter. Oppure potresti non ...

+0

Sono stato incuriosito da questa tecnica ultimamente. Sarà interessante vedere se qualcuno presenta degli inconvenienti. –

+0

Interessante. Non l'avevo considerato un'alternativa ai costruttori complessi. Questa tecnica potrebbe ridurre un numero elevato di sovraccarichi del costruttore. –

+1

Ci sono alcune cose che non si possono fare con ciò che si può fare con i costruttori (ad esempio inizializzando Sali e riferimenti) – Eclipse

0

Non ci penserei. In genere, si pensa all'oggetto 'setter' come a quello.

Inoltre, se si imposta solo l'oggetto, non si dispone di un puntatore ad esso comunque?

2

Non tutti i setter, ma alcuni di loro potrebbero restituire il riferimento all'oggetto per essere utile.

tipo di

a.SetValues(object)(2)(3)(5)("Hello")(1.4); 

Ho usato una volta molto tempo fa per costruire Generatore di espressioni SQL che gestisce tutti i problemi fughe e altre cose.

SqlBuilder builder; 

builder.select(column1)(column2)(column3). 
    where("=")(column1, value1) 
       (column2, value2). 
    where(">")(column3, 100). 
    from(table1)("table2")("table3"); 

Non è stato possibile riprodurre le sorgenti in 10 minuti. Quindi l'implementazione è dietro le quinte.

+0

Ho fatto cose simili per la costruzione di documenti XML in C++. La domanda qui sembra però riguardare i setter della proprietà generale. –

10

È possibile restituire un riferimento a this se si vuole funzione della catena setter chiama insieme in questo modo:

obj.SetCount(10).SetName("Bob").SetColor(0x223344).SetWidth(35); 

Personalmente ritengo che il codice è più difficile da leggere che l'alternativa:

obj.SetCount(10); 
obj.SetName("Bob"); 
obj.SetColor(0x223344); 
obj.SetWidth(35); 
+0

È una questione di layout. Puoi semplicemente scrivere la prima parte come la seconda, omettendo solo obj. su tutto tranne il primo e; su tutto tranne l'ultimo. IMHO, è leggibile allora. –

2

Se la tua motivazione è legata al concatenamento (ad esempio, il suggerimento di Brian Ensink), vorrei offrire due commenti:

1. Se sei tu Ti trovi spesso molte impostazioni contemporaneamente, il che potrebbe significare che dovresti produrre un struct o class che contenga tutte queste impostazioni in modo che possano essere tutte passate contemporaneamente. Il passo successivo potrebbe essere quello di usare questo struct o class nell'oggetto stesso ... ma dal momento che stai usando getter e setter la decisione di come rappresentarla internamente sarà comunque trasparente per gli utenti della classe, quindi questa decisione si riferiscono più alla complessità della classe che a qualsiasi altra cosa.

2. Un'alternativa a un setter è la creazione di un nuovo oggetto, la modifica e il suo ritorno. Questo è sia inefficiente che inappropriato nella maggior parte dei tipi, specialmente nei tipi mutabili. Tuttavia, è un'opzione che le persone a volte dimenticano, nonostante sia usata nella classe di stringhe di molte lingue.

2

Lo scopo tipico di questo stile è in uso per la costruzione di oggetti.

Person* pPerson = &(new Person())->setAge(34).setId(55).setName("Jack"); 

invece di

Person* pPerson = new Person(34, 55, "Jack"); 

Utilizzando il secondo più tradizionale stile si potrebbe dimenticare se il primo valore passato al costruttore era l'età o l'id? Questo può anche portare a più costruttori in base alla validità di alcune proprietà.

Utilizzando il primo stile si potrebbe dimenticare di impostare alcune delle proprietà dell'oggetto e può portare a bug in cui gli oggetti non sono "completamente" costruiti. (Una proprietà di classe viene aggiunta in un secondo momento ma non tutte le posizioni di costruzione vengono aggiornate per chiamare il setter richiesto.)

Come il codice si evolve Mi piace molto il fatto che posso usare il compilatore per aiutarmi a trovare tutti i luoghi dove viene creato un oggetto quando si modifica la firma di un costruttore. Quindi per questo motivo preferisco usare costruttori C++ regolari su questo stile.

Questo modello potrebbe funzionare bene in applicazioni che mantengono la loro datamodel nel corso del tempo in base alle leggi simili a quelle utilizzate in molte applicazioni di database:

  • È possibile aggiungere un campo/attributo a un tavolo/classe che è NULL per impostazione predefinita. (Quindi l'aggiornamento dei dati esistenti richiede solo una nuova colonna NULL nel database.)
  • Il codice che non è modifiche dovrebbe funzionare ugualmente con questo campo NULL aggiunto.
+0

+1 per la sicurezza in fase di compilazione. – Eclipse

+0

Wow. Questo è un incredibile adattamento alla mancanza di parametri con nome in C++ (come Ada ha avuto per 20 anni). –

+0

-> no. per i puntatori però :) –

3

IMO setter sono un odore di codice che di solito indicano una delle due cose:

Fare Un Mountian un granello di sabbia

Se si dispone di una classe come questa:

class Gizmo 
{ 
public: 
    void setA(int a) { a_ = a; } 
    int getA() const { return a_; } 

    void setB(const std::string & b) { v_ = b; } 
    std::string getB() const { return b_; } 
private: 
    std::string b_; 
    int a_; 
}; 

... e i valori sono davvero così semplici, quindi perché non rendere pubblici i dati solo ?:

class Gizmo 
{ 
public: 
    std::string b_; 
    int a_; 
}; 

... molto più semplice e, se i dati sono così semplice si perde nulla.

Un'altra possibilità è che si potrebbe essere

Fare un granello di sabbia da un Mountian

Un sacco di volte i dati non è così semplice: forse bisogna cambiare più valori, fare un po 'di calcolo, avvisare qualche altro oggetto; chissà cosa. Ma se i dati non sono abbastanza banali da avere davvero bisogno dei setter & getter, allora non è abbastanza banale da richiedere anche la gestione degli errori. Quindi in questi casi i tuoi setter & dovrebbero restituire una sorta di codice di errore o fare qualcos'altro per indicare che qualcosa di brutto è successo.

Se si concatenano le chiamate insieme in questo modo:

A.doA().doB().doC(); 

... e DOA() non riesce, non si vuole veramente di essere chiamata DOB() e DOC() in ogni caso? Ne dubito.

+6

RE: "A.doA(). DoB(). DoC();" se doA fallisce, ecco a cosa servono le eccezioni. – Eclipse

+0

+1, buon punto John. Inoltre, buon punto Josh. –

Problemi correlati