Lorsque vous utilisez une méthode de chaînage comme:
var car = new Car().OfBrand(Brand.Ford).OfModel(12345).PaintedIn(Color.Silver).Create();
il peut y avoir deux approches:
Réutilisez le même objet, comme ceci:
public Car PaintedIn(Color color) { this.Color = color; return this; }
Créez un nouvel objet de type
Car
à chaque étape, comme ceci:public Car PaintedIn(Color color) { var car = new Car(this); // Clone the current object. car.Color = color; // Assign the values to the clone, not the original object. return car; }
Le premier est-il faux ou s'agit-il plutôt d'un choix personnel du développeur?
Je crois que sa première approche peut rapidement causer le code intuitif / trompeur. Exemple:
// Create a car with neither color, nor model.
var mercedes = new Car().OfBrand(Brand.MercedesBenz).PaintedIn(NeutralColor);
// Create several cars based on the neutral car.
var yellowCar = mercedes.PaintedIn(Color.Yellow).Create();
var specificModel = mercedes.OfModel(99).Create();
// Would `specificModel` car be yellow or of neutral color? How would you guess that if
// `yellowCar` were in a separate method called somewhere else in code?
Des pensées?
coding-style
readability
method-chaining
Arseni Mourzenko
la source
la source
var car = new Car(Brand.Ford, 12345, Color.Silver);
?Réponses:
Je mettrais l' api couramment à sa propre classe "constructeur", séparée de l'objet qu'elle crée. De cette façon, si le client ne veut pas utiliser l’API fluide, vous pouvez toujours l’utiliser manuellement et il ne pollue pas l’objet du domaine (conformément au principe de la responsabilité unique). Dans ce cas, les éléments suivants seraient créés:
Car
qui est l'objet de domaineCarBuilder
qui tient la API courammentL'utilisation serait comme ceci:
La
CarBuilder
classe ressemblerait à ceci (j'utilise la convention de nommage C # ici):Notez que cette classe ne sera pas thread-safe (chaque thread aura besoin de sa propre instance CarBuilder). Notez également que, même si le langage api fluide est un concept vraiment génial, il est probablement excessif de créer des objets de domaine simples.
Cette offre est plus utile si vous créez une API pour quelque chose de beaucoup plus abstrait et que sa configuration et son exécution sont plus complexes. C'est pourquoi elle fonctionne parfaitement dans les tests unitaires et les infrastructures DI. Vous pouvez voir d'autres exemples dans la section Java de l'article wikipedia Fluent Interface avec les objets persistance, datation et fictif.
MODIFIER:
Comme indiqué dans les commentaires; vous pouvez faire de la classe Builder une classe interne statique (dans la voiture) et la voiture peut être rendue immuable. Cet exemple de laisser la voiture immuable semble un peu ridicule; mais dans un système plus complexe, où vous ne voulez absolument pas changer le contenu de l'objet construit, vous pouvez le faire.
Vous trouverez ci-dessous un exemple illustrant comment utiliser à la fois la classe interne statique et comment gérer une création d'objet immuable qu'elle génère:
L'utilisation serait la suivante:
Edit 2: Pete dans les commentaires a publié un article sur l’utilisation des générateurs avec les fonctions lambda dans le contexte de l’écriture de tests unitaires avec des objets de domaine complexes. C'est une alternative intéressante pour rendre le constructeur un peu plus expressif.
Dans le cas de
CarBuilder
vous devez avoir cette méthode à la place:Qui peut être utilisé comme ceci:
la source
build()
(ouBuild()
), et non le nom du type qu'il crée (Car()
dans votre exemple). De même, siCar
un objet est vraiment immuable (par exemple, tous ses champs le sontreadonly
), même le constructeur ne pourra pas le muter, de sorte que laBuild()
méthode devient responsable de la construction de la nouvelle instance. Une façon de faire est de n'avoirCar
qu'un seul constructeur, qui prend un constructeur comme argument; alors laBuild()
méthode peut simplementreturn new Car(this);
.Ça dépend.
Votre voiture est-elle une entité ou un objet de valeur ? Si la voiture est une entité, l'identité de l'objet est importante. Vous devez donc renvoyer la même référence. Si l'objet est un objet de valeur, il doit être immuable, ce qui signifie que le seul moyen est de renvoyer une nouvelle instance à chaque fois.
Un exemple de ce dernier serait la classe DateTime dans .NET, qui est un objet de valeur.
Cependant, si le modèle est une entité, j'aime bien la réponse de Spoike sur l'utilisation d'une classe de générateur pour construire votre objet. En d'autres termes, cet exemple que vous avez donné n'a de sens qu'à mon humble avis si la voiture est un objet de valeur.
la source
Créez un générateur interne statique distinct.
Utilisez des arguments de constructeur normaux pour les paramètres requis. Et api couramment pour facultatif.
Ne créez pas d'objet lorsque vous définissez la couleur, sauf si vous renommez la méthode NewCarInColour ou quelque chose de similaire.
Je ferais quelque chose comme ceci avec la marque requise et le reste optionnel (c'est du java, mais le votre ressemble à du javascript, mais je suis sûr qu'ils sont interchangeables avec un peu de nit picking):
la source
Le plus important est que quelle que soit la décision que vous choisissez, cela est clairement indiqué dans le nom de la méthode et / ou dans le commentaire.
Il n'y a pas de standard. Parfois, la méthode renvoie un nouvel objet (la plupart des méthodes String le font) ou renvoie cet objet à des fins de chaînage ou d'efficacité de la mémoire.
J'ai déjà conçu un objet vectoriel 3D et les deux méthodes ont été implémentées pour chaque opération mathématique. Pour l'instant la méthode de la balance:
la source
scale
(le mutateur) etscaledBy
(le générateur).Je vois ici quelques problèmes qui, à mon avis, pourraient prêter à confusion ... Votre première phrase de la question:
Vous appelez un constructeur (nouveau) et une méthode create ... Une méthode create () serait presque toujours une méthode statique ou une méthode de générateur, et le compilateur devrait la détecter dans un avertissement ou une erreur pour vous en informer, soit Ainsi, cette syntaxe est soit fausse soit porte des noms terribles. Mais plus tard, vous n'utilisez pas les deux, alors regardons cela.
Encore une fois avec la création, mais pas avec un nouveau constructeur. Le fait est que je pense que vous recherchez plutôt une méthode copy (). Donc, si c'est le cas et qu'il ne s'agit que d'un pauvre nom, examinons une chose ... vous appelez mercedes.Paintedin (Color.Yellow) .Copy () - Il devrait être facile de regarder cela et de dire que c'est en train d'être "peint" 'avant d'être copié - juste un flux de logique normal, pour moi. Alors mettez la copie en premier.
pour moi, il est facile de voir que vous peignez la copie et fabriquiez votre voiture jaune.
la source
La première approche a l'inconvénient que vous avez mentionné, mais tant que vous expliquez clairement dans la documentation, tout codeur semi-compétent ne devrait pas avoir de problèmes. Tout le code de chaine de méthodes avec lequel j'ai personnellement travaillé a fonctionné de cette façon.
La deuxième approche a évidemment l’inconvénient d’être plus de travail. Vous devez également décider si les copies que vous retournez seront des copies superficielles ou profondes: la meilleure solution peut varier d’une classe à l’autre ou d’une méthode à l’autre. Vous allez donc introduire une incohérence ou compromettre le meilleur comportement. Il est à noter que c'est la seule option pour les objets immuables, comme les chaînes.
Quoi que vous fassiez, ne mélangez pas et ne faites pas correspondre dans la même classe!
la source
Je préfère penser comme le mécanisme "Méthodes d'extension".
la source
Ceci est une variation des méthodes ci-dessus. Les différences sont qu'il existe des méthodes statiques sur la classe Car qui correspondent aux noms de méthode sur le générateur, vous n'avez donc pas besoin de créer explicitement un générateur:
Vous pouvez utiliser les mêmes noms de méthode que vous utilisez pour les appels de générateur chaînés:
De plus, il existe une méthode .copy () sur la classe qui retourne un générateur rempli avec toutes les valeurs de l'instance actuelle. Vous pouvez donc créer une variante sur un thème:
Enfin, la méthode .build () du générateur vérifie que toutes les valeurs requises ont été fournies et renvoie le cas échéant. Il serait peut-être préférable d'exiger certaines valeurs sur le constructeur du constructeur et de permettre au reste d'être optionnel; dans ce cas, vous voudriez l'un des modèles dans les autres réponses.
la source