À bien des égards, j’aime beaucoup l’idée des interfaces Fluent, mais avec toutes les fonctionnalités modernes du C # (initialiseurs, lambdas, paramètres nommés), je me dis: "est-ce que ça vaut la peine?" Et "Est-ce le bon modèle pour utilisation?". Est-ce que n'importe qui pourrait me donner, sinon une pratique acceptée, au moins leur propre expérience ou matrice de décision pour quand utiliser le modèle Fluent?
Conclusion:
Quelques bonnes règles empiriques tirées des réponses à ce jour:
- Les interfaces fluides aident beaucoup lorsque vous avez plus d'actions que de paramètres, car les appels bénéficient davantage de la transmission de contexte.
- Les interfaces fluides doivent être considérées comme une couche au-dessus d'une interface API, et non comme le seul moyen d'utilisation.
- Les fonctions modernes telles que les lambdas, les initialiseurs et les paramètres nommés peuvent fonctionner main dans la main pour rendre une interface fluide encore plus conviviale.
Voici un exemple de ce que je veux dire par les fonctionnalités modernes qui permettent de se sentir moins nécessaire. Prenons l'exemple d'une interface Fluent (peut-être médiocre) qui me permet de créer un employé tel que:
Employees.CreateNew().WithFirstName("Peter")
.WithLastName("Gibbons")
.WithManager()
.WithFirstName("Bill")
.WithLastName("Lumbergh")
.WithTitle("Manager")
.WithDepartment("Y2K");
Pourrait facilement être écrit avec des initialiseurs comme:
Employees.Add(new Employee()
{
FirstName = "Peter",
LastName = "Gibbons",
Manager = new Employee()
{
FirstName = "Bill",
LastName = "Lumbergh",
Title = "Manager",
Department = "Y2K"
}
});
J'aurais aussi pu utiliser des paramètres nommés dans les constructeurs de cet exemple.
Réponses:
Écrire une interface fluide (je me suis habitué à cela) demande plus d'effort, mais cela a des avantages, car si vous le faites correctement, l'intention du code utilisateur résultant est plus évidente. C'est essentiellement une forme de langage spécifique à un domaine.
En d'autres termes, si votre code est lu beaucoup plus qu'il n'est écrit (et quel code ne l'est pas?), Vous devriez alors envisager de créer une interface fluide.
Les interfaces Fluent concernent davantage le contexte et constituent bien plus que de simples moyens de configurer des objets. Comme vous pouvez le voir dans le lien ci-dessus, j'ai utilisé une API couramment fluide pour réaliser:
objectA.
intellisense vous donne beaucoup d'indices. Dans mon cas, ci-dessus,plm.Led.
vous donne toutes les options pour contrôler la DEL intégrée etplm.Network.
vous donne les possibilités que vous pouvez utiliser avec l'interface réseau.plm.Network.X10.
Vous donne le sous-ensemble de actions réseau pour les périphériques X10. Vous ne l'obtiendrez pas avec les initialiseurs de constructeur (à moins que vous ne souhaitiez avoir à construire un objet pour chaque type d'action différent, ce qui n'est pas idiomatique).Une chose que je fais généralement est:
Je ne vois pas comment vous pouvez faire quelque chose comme ça aussi bien sans une interface fluide.
Edit 2 : Vous pouvez également apporter des améliorations de lisibilité vraiment intéressantes, comme par exemple:
la source
Scott Hanselman en parle dans l' épisode 260 de son podcast Hanselminutes avec Jonathan Carter. Ils expliquent qu'une interface fluide ressemble davantage à une interface utilisateur sur une API. Vous ne devez pas fournir une interface fluide en tant que seul point d'accès, mais plutôt une interface utilisateur de code au-dessus de "l'interface API standard".
Jonathan Carter parle aussi un peu de la conception des API sur son blog .
la source
Les interfaces Fluent sont des fonctionnalités très puissantes à fournir dans le contexte de votre code, avec le raisonnement "correct".
Si votre objectif est simplement de créer d’énormes chaînes de codes d’une seule ligne comme une sorte de pseudo-boîte noire, vous êtes probablement en train d’aboyer le mauvais arbre. D'autre part, si vous l'utilisez pour ajouter de la valeur à votre interface API en fournissant un moyen d'enchaîner les appels de méthode et d'améliorer la lisibilité du code, alors, avec beaucoup de bonne planification et d'efforts, je pense que l'effort en vaut la peine.
J'éviterais de suivre ce qui semble devenir un "modèle" courant lors de la création d'interfaces fluides, dans lesquelles vous nommez toutes vos méthodes fluides "avec" quelque chose, car cela prive une interface API potentiellement bonne de son contexte, et donc de sa valeur intrinsèque. .
La clé consiste à considérer la syntaxe fluide comme une implémentation spécifique d'un langage spécifique à un domaine. Pour un très bon exemple de ce dont je parle, jetez un coup d’œil à StoryQ, qui utilise la fluidité pour exprimer un DSL de manière très utile et flexible.
la source
position.withX(5)
contreposition.getDistanceToOrigin()
Note initiale: je suis en désaccord avec une hypothèse de la question et j'en tire mes conclusions spécifiques (à la fin de ce post). Parce que cela ne donne probablement pas une réponse complète et globale, je le marque comme CW.
À mes yeux, ces deux versions doivent signifier et faire des choses différentes.
Contrairement à la version non-fluide, la version fluide cache le fait que la nouvelle
Employee
est égalementAdd
liée à la collectionEmployees
- elle suggère seulement qu'un nouvel objet estCreate
d.La signification de
….WithX(…)
est ambiguë, en particulier pour les personnes issues de F #, qui possède unwith
mot - clé pour les expressions d'objet : elles peuvent être interprétéesobj.WithX(x)
comme un nouvel objet dérivé deobj
identique à l'obj
exception de saX
propriété, dont la valeur estx
. D'autre part, avec la deuxième version, il est clair qu'aucun objet dérivé n'est créé et que toutes les propriétés sont spécifiées pour l'objet d'origine.Cela
….With…
a encore un autre sens: changer le "focus" de l'initialisation de la propriété sur un autre objet. Le fait que votre API parle couramment a deux significations différentes,With
ce qui rend difficile l'interprétation correcte de ce qui se passe… C'est peut-être pour cette raison que vous avez utilisé l'indentation dans votre exemple pour démontrer la signification de ce code. Ce serait plus clair comme ça:Conclusions: "Cacher" une fonctionnalité de langage assez simple
new T { X = x }
, avec un API courant (Ts.CreateNew().WithX(x)
) peut évidemment être fait, mais:Il faut veiller à ce que les lecteurs du code qui en résulte comprennent toujours ce qu’il fait exactement. C'est-à-dire que l'IPA fluide devrait avoir une signification transparente et sans ambiguïté. La conception d'une telle API peut nécessiter plus de travail que prévu (il devrait éventuellement être testé pour sa facilité d'utilisation et son acceptation), et / ou…
la conception peut prendre plus de temps que nécessaire: dans cet exemple, l'API fluide ajoute très peu de "confort d'utilisation" par rapport à l'API sous-jacent (une fonctionnalité de langage). On pourrait dire qu'une API fluide devrait rendre la fonctionnalité API / langage sous-jacente "plus facile à utiliser"; c'est-à-dire que cela devrait économiser beaucoup d'effort au programmeur. S'il ne s'agit que d'une autre manière d'écrire la même chose, cela n'en vaut probablement pas la peine, car cela ne simplifie pas la vie du programmeur, mais rend le travail du concepteur plus difficile (voir la conclusion n ° 1 ci-dessus).
Les deux points ci-dessus supposent que l'API fluide est une couche superposée à une API existante ou à une fonctionnalité de langage. Cette hypothèse est peut-être une autre bonne directive: une API fluide peut être un moyen supplémentaire de faire quelque chose, et non la seule. En d'autres termes, il peut être judicieux de proposer une API fluide en tant que choix "opt-in".
la source
J'aime le style fluide, il exprime l'intention très clairement. Avec l'exemple d'initiateur d'objet que vous avez après, vous devez disposer de personnes qui définissent les propriétés publiques pour utiliser cette syntaxe, mais pas avec le style fluide. En disant cela, avec votre exemple, vous ne gagnez pas grand-chose au public parce que vous avez presque opté pour un style de méthode set / get java-esque.
Ce qui m'amène au deuxième point. Je ne suis pas sûr si j'utiliserais le style couramment utilisé, avec de nombreux régleurs de propriété. J'utiliserais probablement la deuxième version pour cela. Je le trouve mieux lorsque vous avoir beaucoup de verbes à chaîner ensemble, ou au moins beaucoup de choses plutôt que de paramètres.
la source
Je ne connaissais pas le terme interface fluide , mais cela me rappelle quelques API que j'ai utilisées, notamment LINQ .
Personnellement, je ne vois pas en quoi les fonctionnalités modernes de C # empêcheraient l’utilité d’une telle approche. Je dirais plutôt qu'ils vont de pair. Par exemple, il est encore plus facile de réaliser une telle interface en utilisant des méthodes d'extension .
Clarifiez peut-être votre réponse avec un exemple concret de la façon dont une interface fluide peut être remplacée en utilisant l’une des fonctionnalités modernes que vous avez mentionnées.
la source