Sue est la conception d' une bibliothèque JavaScript, Magician.js
. Son pivot est une fonction qui extrait un Rabbit
de l'argument passé.
Elle sait que ses utilisateurs peuvent vouloir retirer un lapin d'un String
, d'un Number
, d'un Function
, peut-être même d'un HTMLElement
. Dans cet esprit, elle pourrait concevoir son API comme suit:
L'interface stricte
Magician.pullRabbitOutOfString = function(str) //...
Magician.pullRabbitOutOfHTMLElement = function(htmlEl) //...
Chaque fonction de l'exemple ci-dessus saurait comment gérer l'argument du type spécifié dans le nom de fonction / nom de paramètre.
Ou, elle pourrait le concevoir comme ceci:
L'interface "ad hoc"
Magician.pullRabbit = function(anything) //...
pullRabbit
devrait tenir compte de la variété des différents types attendus que l' anything
argument pourrait être, ainsi que (bien sûr) d'un type inattendu:
Magician.pullRabbit = function(anything) {
if (anything === undefined) {
return new Rabbit(); // out of thin air
} else if (isString(anything)) {
// more
} else if (isNumber(anything)) {
// more
}
// etc.
};
Le premier (strict) semble plus explicite, peut-être plus sûr et peut-être plus performant - car il y a peu ou pas de frais généraux pour la vérification de type ou la conversion de type. Mais ce dernier (ad hoc) se sent plus simple à regarder de l'extérieur, en ce sens qu'il "fonctionne" avec n'importe quel argument que le consommateur d'API trouve commode de lui transmettre.
Pour répondre à cette question , je voudrais voir les pros de spécifiques et les inconvénients de ces deux approches (ou à une approche tout à fait différente, si ni est idéal), que Sue devrait savoir quelle approche adopter lors de la conception de l' API de sa bibliothèque.
la source
Réponses:
Quelques avantages et inconvénients
Avantages pour polymorphe:
Avantages pour ad-hoc:
Cat
instance? Est-ce que ça marcherait? sinon, quel est le comportement? Si je ne limite pas le type ici, je dois le faire dans la documentation ou dans les tests qui pourraient aggraver le contrat.pullRabbitOutOfString
version est susceptible d'être beaucoup plus rapide dans des moteurs comme le V8. Voir cette vidéo pour plus d'informations. Edit: j'ai écrit moi-même un perf, il s'avère qu'en pratique, ce n'est pas toujours le cas .Quelques solutions alternatives:
À mon avis, ce type de conception n'est pas très «Java-Scripty» pour commencer. JavaScript est un langage différent avec des idiomes différents de langages comme C #, Java ou Python. Ces idiomes trouvent leur origine dans des années de développeurs essayant de comprendre les parties faibles et fortes du langage, ce que je ferais, c'est essayer de m'en tenir à ces idiomes.
Il y a deux belles solutions auxquelles je peux penser:
Solution 1: élévation d'objets
Une solution courante à ce problème consiste à «élever» des objets avec la possibilité d'en retirer des lapins.
Autrement dit, avoir une fonction qui prend un certain type d'objet, et ajoute le retrait d'un chapeau pour cela. Quelque chose comme:
Je peux faire de telles
makePullable
fonctions pour d'autres objets, je pourrais créer unmakePullableString
, etc. Je définis la conversion sur chaque type. Cependant, après avoir élevé mes objets, je n'ai plus de type pour les utiliser de manière générique. Une interface en JavaScript est déterminée par une saisie de canard, si elle a unepullOfHat
méthode, je peux la tirer avec la méthode du magicien.Le magicien pourrait alors faire:
Élever des objets, en utilisant une sorte de modèle de mixage semble être la chose la plus JS à faire. (Notez que cela pose problème avec les types de valeurs dans la langue qui sont des chaînes, des nombres, des valeurs nulles, non définies et booléennes, mais ils sont tous compatibles avec les boîtes)
Voici un exemple de ce à quoi pourrait ressembler un tel code
Solution 2: modèle de stratégie
En discutant de cette question dans la salle de chat JS dans StackOverflow, mon ami phénoménominal a suggéré l'utilisation du modèle de stratégie .
Cela vous permettrait d'ajouter des capacités pour retirer des lapins de divers objets au moment de l'exécution, et créerait du code très JavaScript. Un magicien peut apprendre à retirer des objets de différents types de chapeaux, et il les tire en fonction de ces connaissances.
Voici à quoi cela pourrait ressembler dans CoffeeScript:
Vous pouvez voir le code JS équivalent ici .
De cette façon, vous bénéficiez des deux mondes, l'action de tirer n'est pas étroitement liée aux objets ou au magicien et je pense que cela constitue une très bonne solution.
L'utilisation serait quelque chose comme:
la source
Le problème est que vous essayez d'implémenter un type de polymorphisme qui n'existe pas dans JavaScript - JavaScript est presque universellement mieux traité comme un langage de type canard, même s'il prend en charge certaines facultés de type.
Pour créer la meilleure API, la réponse est que vous devez implémenter les deux. C'est un peu plus de frappe, mais cela économisera beaucoup de travail à long terme pour les utilisateurs de votre API.
pullRabbit
devrait simplement être une méthode d'arbitre qui vérifie les types et appelle la fonction appropriée associée à ce type d'objet (par exemplepullRabbitOutOfHtmlElement
).De cette façon, alors que les utilisateurs de prototypage peut utiliser
pullRabbit
, mais s'ils remarquent un ralentissement , ils peuvent mettre en œuvre le type de vérification à leur fin (en probablement un moyen plus rapide) et il suffit d' appelerpullRabbitOutOfHtmlElement
directement.la source
C'est JavaScript. Au fur et à mesure que vous l'améliorez, vous constaterez qu'il existe souvent une voie médiane qui aide à éliminer les dilemmes comme celui-ci. De plus, peu importe si un «type» non pris en charge est pris par quelque chose ou se casse lorsque quelqu'un essaie de l'utiliser parce qu'il n'y a pas de compilation par rapport à l'exécution. Si vous l'utilisez mal, il se casse. Essayer de cacher qu'il s'est cassé ou de le faire fonctionner à mi-chemin quand il s'est cassé ne change rien au fait que quelque chose est cassé.
Alors, prenez votre gâteau et mangez-le aussi et apprenez à éviter la confusion de type et la casse inutile en gardant tout vraiment, vraiment évident, comme bien nommé et avec tous les bons détails aux bons endroits.
Tout d'abord, j'encourage fortement à prendre l'habitude de mettre vos canards en rang avant de devoir vérifier les types. La chose la plus simple et la plus efficace (mais pas toujours la meilleure en ce qui concerne les constructeurs natifs) serait de frapper les prototypes en premier afin que votre méthode n'ait même pas à se soucier du type pris en charge en jeu.
Remarque: Il est largement considéré comme une mauvaise forme de faire cela à Object car tout hérite d'Object. Personnellement, j'éviterais aussi Function. Certains peuvent se sentir inquiets de toucher un prototype de constructeur natif, ce qui n'est peut-être pas une mauvaise politique, mais l'exemple peut toujours servir lorsque vous travaillez avec vos propres constructeurs d'objets.
Je ne m'inquiéterais pas de cette approche pour une telle méthode d'utilisation spécifique qui ne risque pas de gâcher quelque chose d'une autre bibliothèque dans une application moins compliquée, mais c'est un bon instinct pour éviter d'affirmer quoi que ce soit trop généralement entre les méthodes natives de JavaScript si vous ne le faites pas. à moins que vous ne normalisiez de nouvelles méthodes dans des navigateurs obsolètes.
Heureusement, vous pouvez toujours simplement mapper des types ou des noms de constructeur aux méthodes (méfiez-vous IE <= 8 qui n'a pas <object> .constructor.name vous obligeant à l'analyser des résultats toString de la propriété constructeur). Vous êtes toujours en train de vérifier le nom du constructeur (typeof est un peu inutile dans JS lorsque vous comparez des objets) mais au moins, cela se lit beaucoup mieux qu'une déclaration de commutateur géant ou si / autre chaîne dans chaque appel de la méthode à ce qui pourrait être un large variété d'objets.
Ou en utilisant la même approche de carte, si vous vouliez accéder au composant «this» des différents types d'objets pour les utiliser comme s'il s'agissait de méthodes sans toucher à leurs prototypes hérités:
Un bon principe général dans n'importe quel langage IMO est d'essayer de trier les détails de branchement comme celui-ci avant d'arriver au code qui tire réellement le déclencheur. De cette façon, il est facile de voir tous les joueurs impliqués à ce niveau d'API le plus élevé pour une belle vue d'ensemble, mais aussi beaucoup plus facile de trier où les détails dont quelqu'un pourrait se soucier sont susceptibles d'être trouvés.
Remarque: tout cela n'est pas testé, car je suppose que personne n'a réellement d'utilisation de RL pour cela. Je suis sûr qu'il y a des fautes de frappe / bugs.
la source
C'est (pour moi) une question intéressante et compliquée à répondre. En fait, j'aime cette question, donc je ferai de mon mieux pour y répondre. Si vous faites des recherches sur les normes de programmation javascript, vous trouverez autant de "bonnes" façons de le faire qu'il y a de gens qui vantent la "bonne" façon de le faire.
Mais puisque vous cherchez une opinion sur la meilleure façon. Rien ne va ici.
Personnellement, je préférerais l'approche de conception "adhoc". Issu d'un background c ++ / C #, c'est plus mon style de développement. Vous pouvez créer la seule demande pullRabbit et demander à ce type de demande de vérifier l'argument transmis et de faire quelque chose. Cela signifie que vous n'avez pas à vous soucier du type d'argument transmis à un moment donné. Si vous utilisez l'approche stricte, vous devrez toujours vérifier de quel type est la variable, mais vous le ferez à la place avant de faire l'appel de méthode. Donc, à la fin, la question est de savoir si vous voulez vérifier le type avant de passer l'appel ou après.
J'espère que cela vous aide, n'hésitez pas à poser plus de questions par rapport à cette réponse, je ferai de mon mieux pour clarifier ma position.
la source
Lorsque vous écrivez, Magician.pullRabbitOutOfInt, il documente ce que vous avez pensé lorsque vous avez écrit la méthode. L'appelant s'attendra à ce que cela fonctionne s'il passe un entier. Lorsque vous écrivez, Magician.pullRabbitOutOfAnything, l'appelant ne sait pas quoi penser et doit aller fouiller dans votre code et expérimenter. Cela pourrait fonctionner pour un Int, mais cela fonctionnera-t-il pendant longtemps? Un flotteur? Un double? Si vous écrivez ce code, jusqu'où êtes-vous prêt à aller? Quels types d'arguments êtes-vous prêt à soutenir?
L'ambiguïté prend du temps à comprendre. Je ne suis même pas convaincu qu'il soit plus rapide d'écrire:
Contre:
OK, j'ai donc ajouté une exception à votre code (que je recommande fortement) pour dire à l'appelant que vous n'avez jamais imaginé qu'il vous passerait ce qu'il a fait. Mais écrire des méthodes spécifiques (je ne sais pas si JavaScript vous permet de le faire) n'est plus du code et beaucoup plus facile à comprendre en tant qu'appelant. Il établit des hypothèses réalistes sur ce que l'auteur de ce code a pensé et rend le code facile à utiliser.
la source