Comment créer une API fluide dans la nature?
S'agit-il principalement de méthodes d'extension?
Cet article l'explique bien mieux que jamais.
EDIT, ne peut pas presser cela dans un commentaire ...
il y a deux côtés aux interfaces, l'implémentation et l'utilisation. Il y a plus de travail à faire du côté de la création, je suis d'accord avec cela, cependant les principaux avantages peuvent être trouvés du côté de l'utilisation des choses. En effet, pour moi, le principal avantage des interfaces fluides est une API plus naturelle, plus facile à mémoriser et à utiliser et pourquoi pas, plus esthétique. Et peut-être que l'effort d'avoir à presser une API sous une forme fluide peut conduire à une API mieux pensée?
Comme Martin Fowler le dit dans l'article original sur les interfaces fluides :
La chose probablement la plus importante à noter à propos de ce style est que l'intention est de faire quelque chose dans le sens d'un DomainSpecificLanguage interne. En effet, c'est pourquoi nous avons choisi le terme "fluide" pour le décrire, à bien des égards les deux termes sont synonymes. L'API est principalement conçue pour être lisible et fluide. Le prix de cette fluidité est plus d'efforts, à la fois dans la réflexion et dans la construction de l'API elle-même. L'API simple des méthodes constructeur, setter et addition est beaucoup plus facile à écrire. Venir avec une API couramment Nice nécessite une bonne réflexion.
Comme dans la plupart des cas, les API sont créées une fois et utilisées maintes et maintes fois, l'effort supplémentaire peut en valoir la peine.
Et verbeux? Je suis tout à fait pour la verbosité si elle sert la lisibilité d'un programme.
MrBlah,
Bien que vous puissiez écrire des méthodes d'extension pour écrire une interface fluide, une meilleure approche consiste à utiliser le modèle de générateur. Je suis dans le même bateau que vous et j'essaie de comprendre quelques fonctionnalités avancées des interfaces fluides.
Ci-dessous, vous verrez un exemple de code que j'ai créé dans n autre thread
public class Coffee
{
private bool _cream;
private int _ounces;
public static Coffee Make { get { return new Coffee(); } }
public Coffee WithCream()
{
_cream = true;
return this;
}
public Coffee WithOuncesToServe(int ounces)
{
_ounces = ounces;
return this;
}
}
var myMorningCoffee = Coffee.Make.WithCream().WithOuncesToServe(16);
Alors que de nombreuses personnes citent Martin Fowler comme étant un exposant éminent dans la discussion courante sur l'API, ses premières revendications de conception évoluent en fait autour d'un modèle de constructeur fluide ou chaînage de méthode . Les API fluides peuvent être transformées en véritables langages internes spécifiques au domaine . Un article qui explique comment une notation BNF d'une grammaire peut être transformée manuellement en une "API fluide" peut être vu ici:
http://blog.jooq.org/2012/01/05/the-Java-fluent-api-designer-crash-course/
Il transforme cette grammaire:
Dans ceci Java API:
// Initial interface, entry point of the DSL
interface Start {
End singleWord();
End parameterisedWord(String parameter);
Intermediate1 Word1();
Intermediate2 Word2();
Intermediate3 Word3();
}
// Terminating interface, might also contain methods like execute();
interface End {
void end();
}
// Intermediate DSL "step" extending the interface that is returned
// by optionalWord(), to make that method "optional"
interface Intermediate1 extends End {
End optionalWord();
}
// Intermediate DSL "step" providing several choices (similar to Start)
interface Intermediate2 {
End wordChoiceA();
End wordChoiceB();
}
// Intermediate interface returning itself on Word3(), in order to allow
// for repetitions. Repetitions can be ended any time because this
// interface extends End
interface Intermediate3 extends End {
Intermediate3 Word3();
}
Java et C # étant quelque peu similaires, l'exemple se traduit certainement également dans votre cas d'utilisation. La technique ci-dessus a été largement utilisée dans jOOQ , un langage courant spécifique à un API/domaine interne modélisant le langage SQL en Java
C'est une très vieille question, et cette réponse devrait probablement être un commentaire plutôt qu'une réponse, mais je pense que c'est un sujet qui mérite d'être abordé, et cette réponse est trop longue pour être un commentaire.
La pensée originale concernant la "fluence" semble avoir été essentiellement basée sur l'ajout de puissance et de flexibilité (chaînage de méthodes, etc.) aux objets tout en rendant le code un peu plus explicite.
Par exemple
Company a = new Company("Calamaz Holding Corp");
Person p = new Person("Clapper", 113, 24, "Frank");
Company c = new Company(a, 'Floridex', p, 1973);
est moins "fluide" que
Company c = new Company().Set
.Name("Floridex");
.Manager(
new Person().Set.FirstName("Frank").LastName("Clapper").Awards(24)
)
.YearFounded(1973)
.ParentCompany(
new Company().Set.Name("Calamaz Holding Corp")
)
;
Mais pour moi, ce dernier n'est pas vraiment plus puissant, flexible ou explicite que
Company c = new Company(){
Name = "Floridex",
Manager = new Person(){ FirstName="Frank", LastName="Clapper", Awards=24 },
YearFounded = 1973,
ParentCompany = new Company(){ Name="Calamaz Holding Corp." }
};
..en fait, j'appellerais cette dernière version plus facile à créer, à lire et à entretenir que la précédente, et je dirais qu'elle nécessite également beaucoup moins de bagages en coulisses. Ce qui est important pour moi, pour (au moins) deux raisons:
1 - Le coût associé à la création et à la maintenance de couches d'objets (peu importe qui le fait) est tout aussi réel, pertinent et important que le coût associé à la création et à la maintenance du code qui les consomme.
2 - Le ballonnement de code intégré dans des couches d'objets crée autant (sinon plus) de problèmes que le ballonnement de code dans le code qui consomme ces objets.
L'utilisation de la dernière version signifie que vous pouvez ajouter une propriété (potentiellement utile) à la classe Company en ajoutant simplement une ligne de code très simple.
Cela ne veut pas dire que je pense qu'il n'y a pas de place pour le chaînage de méthodes. J'aime vraiment pouvoir faire des choses comme (en JavaScript)
var _this = this;
Ajax.Call({
url: '/service/getproduct',
parameters: {productId: productId},
)
.Done(
function(product){
_this.showProduct(product);
}
)
.Fail(
function(error){
_this.presentError(error);
}
);
..where (dans le cas hypothétique que j'imagine) Done et Fail étaient des ajouts à l'objet Ajax d'origine, et ont pu être ajoutés sans changer le code de l'objet Ajax d'origine ou le code existant qui utilisait le objet Ajax d'origine, et sans créer des choses uniques qui étaient des exceptions à l'organisation générale du code.
J'ai donc certainement trouvé utile de faire en sorte qu'un sous-ensemble des fonctions d'un objet retourne l'objet "this". En fait, chaque fois que j'ai une fonction qui retournerait sinon nulle, je pense à la renvoyer.
Mais je n'ai pas encore trouvé de valeur significative en ajoutant des "interfaces fluentes" (.eg "Set") à un objet, bien qu'en théorie il semble qu'il puisse y avoir une sorte d'organisation de code de type espace de nom qui pourrait découler de la pratique de le faire, ce qui pourrait être utile. ("Définir" n'est peut-être pas particulièrement utile, mais "Commande", "Requête" et "Transfert" peuvent, s'ils aident à organiser les choses et à faciliter et minimiser l'impact des ajouts et des modifications.) L'un des avantages potentiels d'une telle pratique , selon la façon dont cela a été fait, pourrait être une amélioration du niveau typique de soin et d'attention d'un codeur pour les niveaux de protection - dont l'absence a certainement causé beaucoup de chagrin.
KISS: Gardez les choses stupides.
La conception fluide correspond à un principe de conception esthétique utilisé dans toute l'API. La méthodologie que vous utilisez dans votre API peut légèrement changer, mais il est généralement préférable de rester cohérent.
Même si vous pensez que "tout le monde peut utiliser cette API, car elle utilise tous les différents types de méthodologie". La vérité est que l'utilisateur commencerait à se sentir perdu parce que vous changez constamment la structure/structure de données de l'API en un nouveau principe de conception ou convention de dénomination.
Si vous souhaitez passer à mi-chemin à un principe de conception différent, par exemple .. Conversion de codes d'erreur à la gestion des exceptions, car une puissance de commande plus élevée. Ce serait de la folie et cela entraînerait normalement beaucoup de souffrance. Il vaut mieux garder le cap et ajouter des fonctionnalités que vos clients peuvent utiliser et vendre que de les faire réécrire et redécouvrir tous leurs problèmes.
À la suite de ce qui précède, vous pouvez voir qu'il y a plus à faire pour écrire une API Fluent que pour le plaisir. Il y a des choix psychologiques et esthétiques à faire avant de commencer à en écrire un et même alors, le sentiment, le besoin et le désir de se conformer à la demande des clients et de rester cohérent est le plus difficile de tous.
Qu'est-ce qu'une API fluide
Wikipédia les définit ici http://en.wikipedia.org/wiki/Fluent_interface
Pourquoi ne pas utiliser une interface fluide
Je suggérerais de ne pas implémenter une interface fluide traditionnelle, car cela augmente la quantité de code dont vous avez besoin pour écrire, complique votre code et ajoute simplement un passe-partout inutile.
Une autre option, ne faites rien!
N'implémentez rien. Ne fournissez pas de constructeurs "faciles" pour définir les propriétés et ne fournissez pas d'interface intelligente pour aider votre client. Autorisez le client à définir les propriétés comme il le ferait normalement. Dans .Net C # ou VB cela peut être aussi simple que d'utiliser initialiseurs d'objet .
Car myCar = new Car { Name = "Chevrolet Corvette", Color = Color.Yellow };
Vous n'avez donc pas besoin de créer d'interface intelligente dans votre code, ce qui est très lisible.
Si vous avez des propriétés Sets très complexes qui doivent être définies, ou définies dans un certain ordre, utilisez un objet de configuration distinct et passez-le à la classe via une propriété distincte.
CarConfig conf = new CarConfig { Color = Color.Yellow, Fabric = Fabric.Leather };
Car myCar = new Car { Config = conf };
Écrire une API couramment c'est compliqué, c'est pourquoi j'ai écrit Diezel qui est un générateur d'API Fluent pour Java. Il génère l'API avec des interfaces (ou cours) pour:
Il génère également des implémentations.
C'est un plugin maven.
Avec une API fluide:
myCar.SetColor(Color.Blue).SetName("Aston Martin");
Découvrez cette vidéo http://www.viddler.com/explore/dcazzulino/videos/8/
Non et oui. Les bases sont une bonne interface ou des interfaces pour les types que vous souhaitez vous comporter couramment. Les bibliothèques avec des méthodes d'extension peuvent étendre ce comportement et renvoyer l'interface. Les méthodes d'extension donnent aux autres la possibilité d'étendre votre API couramment avec plus de méthodes.
Une bonne conception fluide peut être difficile et nécessite une période d'essai et d'erreur assez longue pour affiner totalement les blocs de construction de base. Juste une API fluide pour la configuration ou l'installation n'est pas si difficile.
Apprendre à créer une API fluide le fait en examinant les API existantes. Comparez FluentNHibernate avec les API .NET fluides ou les interfaces fluides ICriteria. De nombreuses API de configuration sont également conçues "couramment".