2009-04-15 4 views

Répondre

18

C'est un modèle suffisamment utilisable s'il y a beaucoup de choses qui doivent être définies sur un objet.

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); 
} 

Ce modèle remplace un constructeur qui prend trois ints:

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

Il est utile si vous avez un certain nombre de valeurs qui ne doivent pas toujours être réglé.

Pour référence, un exemple plus complet de ce type de technique est référencé comme "Named Parameter Idiom" dans la FAQ C++ Lite.

Bien sûr, si vous utilisez ceci pour des paramètres nommés, vous pouvez jeter un oeil à boost::parameter. Ou vous pourriez ne pas ...

+0

J'ai récemment été intrigué par cette technique. Ce sera intéressant de voir si quelqu'un a des inconvénients. –

+0

Intéressant. Je n'avais pas considéré cela comme une alternative aux constructeurs complexes. Cette technique pourrait réduire le nombre de surcharges de constructeurs. –

+1

Il y a certaines choses que vous ne pouvez pas faire avec ceci que vous pouvez faire avec les constructeurs (par exemple l'initialisation des consts et des références) – Eclipse

0

Je ne le pense pas. En règle générale, vous pensez que l'objet 'setter' fait exactement cela. En outre, si vous venez de définir l'objet, ne disposez-vous pas d'un pointeur de toute façon?

2

Tous les setters, mais certains d'entre eux pourraient renvoyer la référence à l'objet pour être utile.

genre de

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

J'ai utilisé ce temps il y a une fois de temps pour construire le constructeur d'expression SQL qui gère tous les problèmes et autres évasions.

SqlBuilder builder; 

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

Je n'ai pas pu reproduire les sources en 10 minutes. Donc, la mise en œuvre est derrière les rideaux.

+0

J'ai fait des choses similaires pour construire des documents XML en C++. La question ici semble cependant concerner les créateurs de propriété en général. –

10

Vous pouvez renvoyer une référence à this si vous voulez la fonction setter de la chaîne appelle ensemble comme ceci:

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

Personnellement, je pense que le code est plus difficile à lire que l'alternative:

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

C'est une question de mise en page. Vous pouvez simplement écrire la première partie comme la seconde, en omettant obj. sur tout sauf le premier et; sur tout sauf le dernier. À mon humble avis, il est aussi lisible alors. –

2

Si votre motivation est liée à l'enchaînement (par exemple la suggestion de Brian Ensink), je voudrais offrir deux commentaires:

1. Si oui Vous trouverez souvent des réglages de plusieurs choses à la fois, ce qui peut signifier que vous devriez produire un struct ou class qui contient tous ces paramètres afin qu'ils puissent tous être transmis en même temps. La prochaine étape pourrait être d'utiliser struct ou class dans l'objet lui-même ... mais puisque vous utilisez des getters et des setters, la décision de le représenter en interne sera transparente pour les utilisateurs de la classe, donc cette décision relier plus à la complexité de la classe que quoi que ce soit.

2. Une alternative à un setter consiste à créer un nouvel objet, à le modifier et à le renvoyer. Ceci est à la fois inefficace et inapproprié dans la plupart des types, en particulier les types mutables. Cependant, c'est une option que les gens oublient parfois, malgré son utilisation dans la classe de chaînes de plusieurs langues.

2

Le but typique de ce style est utilisé pour la construction d'objets.

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

au lieu de

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

Utilisation du deuxième style plus traditionnel on peut oublier si la première valeur passée au constructeur était l'âge ou l'id? Cela peut également conduire à plusieurs constructeurs basés sur la validité de certaines propriétés. En utilisant le premier style, on peut oublier de définir certaines des propriétés de l'objet et peut conduire à des bogues où les objets ne sont pas «entièrement» construits. (Une propriété de classe est ajoutée ultérieurement mais tous les emplacements de construction n'ont pas été mis à jour pour appeler le setter requis.)

Comme le code évolue, j'aime vraiment le fait que je peux utiliser le compilateur pour m'aider à trouver tous les endroits où un objet est créé lors de la modification de la signature d'un constructeur. Donc, pour cette raison, je préfère utiliser les constructeurs C++ classiques sur ce style.

Ce modèle pourrait bien fonctionner dans des applications qui maintiennent leur datamodel au fil du temps selon des règles similaires à celles utilisées dans de nombreuses applications de base de données:

  • Vous pouvez ajouter un champ/attribut à une table/classe qui est NULL par défaut. (La mise à niveau des données existantes requiert donc simplement une nouvelle colonne NULL dans la base de données.)
  • Le code qui ne change pas devrait toujours fonctionner de la même manière avec ce champ NULL ajouté.
+0

+1 pour la sécurité à la compilation. – Eclipse

+0

Wow. C'est une adaptation étonnante au manque de paramètres nommés de C++ (comme Ada l'a fait depuis 20 ans). –

+0

-> pas. pour les pointeurs si :) –

3

OMI setters sont une odeur de code qui indiquent généralement l'une des deux choses:

Faire un Mountian d'une taupinière

Si vous avez une classe comme ceci:

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_; 
}; 

... et les valeurs sont vraiment aussi simples, alors pourquoi ne pas simplement rendre les membres de données publics ?:

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

... Beaucoup plus simple et, si les données sont aussi simple que cela vous ne perdez rien.

Une autre possibilité est que vous pourriez être

Faire une taupinière Sur un Mountian

Beaucoup de fois que les données n'est pas aussi simple que cela: peut-être vous devez changer plusieurs valeurs, faire un peu de calcul, notifier un autre objet; qui sait quoi. Mais si les données sont assez triviales pour que vous ayez vraiment besoin de getters, alors c'est assez non-trivial pour avoir besoin de la gestion des erreurs. Donc, dans ces cas, vos setters & devraient renvoyer un code d'erreur ou faire quelque chose d'autre pour indiquer que quelque chose de mal est arrivé.

Si vous enchaînez les appels ensemble comme ceci:

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

... et DOA() tombe en panne, voulez-vous vraiment à appeler DOB() et doC() de toute façon? J'en doute.

+6

RE: "A.doA(). DoB(). DoC();" Si DOA échoue, c'est ce que sont les exceptions. – Eclipse

+0

+1, bon point John. Aussi bon point Josh. –

Questions connexes