web-dev-qa-db-fra.com

Quand une méthode d'une classe doit-elle renvoyer la même instance après sa modification?

J'ai une classe qui a trois méthodes A(), B() et C(). Ces méthodes modifient la propre instance.

Bien que les méthodes doivent renvoyer une instance lorsque l'instance est une copie séparée (juste comme Clone()), j'ai un choix libre de retourner void ou la même instance (return this;) Lorsque vous modifiez la même instance dans la méthode et ne renvoyez aucune autre valeur.

Lorsque vous décidez de retourner la même instance modifiée, je peux faire des chaînes de méthodes soiantes telles que obj.A().B().C();.

Serait-ce la seule raison de le faire?

Est-ce même bon de modifier la propre instance et de le renvoyer aussi? Ou devrait-il seulement renvoyer une copie et laisser l'objet d'origine comme avant? Parce que lors du retour de la même instance modifiée, l'utilisateur supposerait peut-être que la valeur renvoyée est une copie, sinon elle ne serait pas retournée? Si ça va, quelle est la meilleure façon de clarifier de telles choses sur la méthode?

9
Martin Braun

Quand utiliser enchaînant

La chaîne de fonctionnement est principalement populaire auprès des langues où an IDE avec auto-complet est un lieu commun. Par exemple, presque tous les développeurs C # utilisent Visual Studio. Par conséquent, si vous développez avec C # Ajout de chaînage à Vos méthodes peuvent être une économiseur de temps pour les utilisateurs de cette classe car Visual Studio vous aidera à construire la chaîne.

D'autre part, des langues comme PHP "de nature hautement dynamique et ne disposent souvent pas de support automatique dans les IDes verront moins de classes qui soutiennent la chaîne. La chaînage ne sera approprié que lorsque c'est correct Les phpdocs sont utilisés pour exposer les méthodes de chaîne.

Qu'est-ce que le chaînage?

Compte tenu d'une classe nommée Foo Les deux méthodes suivantes sont à la fois chaînées.

function what() { return this; }
function when() { return new Foo(this); }

Le fait que l'on soit une référence à l'instance actuelle et que l'on crée une nouvelle instance ne modifie pas que ce sont des méthodes de chaîne.

Il n'y a pas de règle d'or qu'une méthode de chaîne ne doit indiquer que l'objet actuel. Enfait, les méthodes de chaîne peuvent être sur deux classes différentes. Par exemple;

class B { function When() { return true; } };
class A { function What() { return new B(); } };

var a = new A();
var x = a.What().When();

Il n'y a aucune référence à this dans l'un de l'exemple ci-dessus. Le code a.What().When() est un exemple de chaînage. Ce qui est intéressant, c'est que le type de classe B n'est jamais attribué à une variable.

Une méthode est chaînée quand sa valeur de retour devient utilisée comme composante suivante d'une expression.

Voici un autre exemple

 // return value never assigned.
 myFile.Open("something.txt").Write("stuff").Close();

// two chains used in expression
int x = a.X().Y() * b.X().Y();

// a chain that creates new strings
string name = str.Substring(1,10).Trim().ToUpperCase();

Quand utiliser this et new(this)

Les chaînes dans la plupart des langues sont immuables. Ainsi, les appels de méthode de chaînage entraînent toujours de nouvelles chaînes créées. Où comme un objet comme StressBuilder peut être modifié.

La cohérence est la meilleure pratique.

Si vous avez des méthodes qui modifient l'état d'un objet et de retour this, ne mélangez pas de méthodes qui renvoient de nouvelles instances. Au lieu de cela, créez une méthode spécifique appelée Clone() qui le fera explicitement.

 var x  = a.Foo().Boo().Clone().Foo();

C'est beaucoup plus clair quant à ce qui se passe à l'intérieur a.

Le pas à l'extérieur et le tour arrière

J'appelle cela le Step Out and Back Trick, car il résout beaucoup de problèmes courants liés à la chaîne. Cela signifie fondamentalement que vous sortez de la classe d'origine dans une nouvelle classe temporaire, puis de retour à la classe d'origine.

La classe temporaire n'existe que pour fournir des fonctionnalités spéciales à la classe d'origine, mais uniquement dans des conditions spéciales.

Il y a souvent des moments où une chaîne doit changer -State, mais la classe A ne peut pas représenter tous les états possibles . Donc, pendant une chaîne, une nouvelle classe est introduite qui contient une référence à A. Cela permet au programmeur d'entrer dans un état et de retour à A.

Voici mon exemple, laissez l'État spécial être connu sous le nom de B.

class A {
    function Foo() { return this; }
    function Boo() { return this; }
    function Change() return new B(this); }
}

class B {
    var _a;
    function (A) { _a = A; }
    function What() { return this; }
    function When() { return this; }
    function End() { return _a; }
}

var a = new A();
a.Foo().Change().What().When().End().Boo();

Maintenant c'est un exemple très simple. Si vous vouliez avoir plus de contrôle, alors B peut revenir à un nouveau super-type de A qui a des méthodes différentes.

7
Reactgular

Ces méthodes modifient la propre instance.

Selon la langue, avoir des méthodes qui renvoient void/unit et modifient leur instance ou leur paramètre est non idiomatique. Même dans des langues qui l'avaient utilisé pour le faire plus (C #, C++), il sort de la mode avec le passage à une programmation de style plus fonctionnelle (objets immuables, fonctions pures). Mais supposons qu'il y a une bonne raison pour le moment.

Serait-ce la seule raison de le faire?

Pour certains comportements (pensez x++) On s'attend à ce que l'opération renvoie le résultat, même s'il modifie la variable. Mais c'est en grande partie la seule raison de le faire seul.

Est-ce même bon de modifier la propre instance et de le renvoyer aussi? Ou devrait-il seulement renvoyer une copie et laisser l'objet d'origine comme avant? Parce que lorsque vous renvoyez la même instance modifiée, l'utilisateur admettrait peut-être que la valeur renvoyée est une copie, sinon elle ne serait pas retournée? Si ça va, quelle est la meilleure façon de clarifier de telles choses sur la méthode?

Ça dépend.

Dans les langues où la copie/nouveau et le retour est courante (C # Linq et Strings), le retour de la même référence serait confus. Dans les langues où modifier et revenir est courant (certaines bibliothèques C++), la copie serait confuse.

Faire la signature sans ambiguïté (en retournant void ou en utilisant des constructions de langue comme des propriétés) serait la meilleure façon de clarifier. Après cela, faire le nom clair comme SetFoo pour montrer que vous modifiez l'instance existante est bonne. mais la clé est de maintenir les idiomes de la langue/la bibliothèque avec laquelle vous travaillez.

3
Telastyn

(J'ai supposé C++ comme langue de programmation)

Pour moi, c'est surtout un aspect lisibilité. Si A, B, C est des modificateurs, surtout s'il s'agit d'un objet temporaire transmis comme un paramètre à une fonction, par exemple.

do_something(
      CPerson('name').age(24).height(160).weight(70)
      );

par rapport à

{
   CPerson person('name');
   person.set_age(24);
   person.set_height(160);
   person.set_weight(70);
   do_something(person);
}

Regradant s'il est correct de renvoyer une référence à l'instance modifiée, je dirais oui et vous indiquez par exemple les opérateurs de flux '>>' et '<<' ( http: //www.cprogramming. com/tutoriel/opérateur_overloading.html )

0
AdrianI

Vous pouvez également effectuer la méthode enchaînant la tenue de la copie plutôt que de modifier le retour.

Un excellent exemple C # est String.replace (A, B) qui ne change pas la chaîne sur laquelle elle a été appelée mais renvoie plutôt une nouvelle chaîne vous permettant de la chaîne de manière joyeuse.

0
Peter Wone