Quelques avantages et inconvénients
Avantages pour polymorphe:
- Une interface polymorphe plus petite est plus facile à lire. Je n'ai qu'à me souvenir d'une méthode.
- Cela va avec la façon dont la langue est censée être utilisée - Duck typing.
- S'il est clair de quels objets je veux sortir un lapin, il ne devrait pas y avoir d'ambiguïté de toute façon.
- Faire beaucoup de vérification de type est considéré comme mauvais même dans des langages statiques comme Java, où avoir beaucoup de vérifications de type pour le type d'objet rend le code laid, si le magicien a vraiment besoin de différencier le type d'objets dont il tire un lapin de ?
Avantages pour ad-hoc:
- C'est moins explicite, puis-je extraire une chaîne d'une
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.
- Vous avez toute la manipulation de tirer un lapin en un seul endroit, le magicien (certains pourraient considérer cela comme un con)
- Les optimiseurs JS modernes différencient les fonctions monomorphes (ne fonctionnent que sur un seul type) et polymorphes. Ils savent comment optimiser les monomorphes beaucoup mieux, donc la
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:
- Élever des objets, rendre des objets «extractibles», les rendre conformes à une interface lors de l'exécution, puis faire travailler le magicien sur des objets extractibles.
- En utilisant le modèle de stratégie, apprendre au magicien de manière dynamique comment gérer différents types d'objets.
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:
function makePullable(obj){
obj.pullOfHat = function(){
return new Rabbit(obj.toString());
}
}
Je peux faire de telles makePullable
fonctions pour d'autres objets, je pourrais créer un makePullableString
, 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 une pullOfHat
méthode, je peux la tirer avec la méthode du magicien.
Le magicien pourrait alors faire:
Magician.pullRabbit = function(pullable) {
var rabbit = obj.pullOfHat();
return {rabbit:rabbit,text:"Tada, I pulled a rabbit out of "+pullable};
}
É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:
class Magician
constructor: ()-> # A new Magician can't pull anything
@pullFunctions = {}
pullRabbit: (obj) -> # Pull a rabbit, handler based on type
func = pullFunctions[obj.constructor.name]
if func? then func(obj) else "Don't know how to pull that out of my hat!"
learnToPull: (obj, handler) -> # Learns to pull a rabbit out of a type
pullFunctions[obj.constructor.name] = handler
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:
var m = new Magician();//create a new Magician
//Teach the Magician
m.learnToPull("",function(){
return "Pulled a rabbit out of a string";
});
m.learnToPull({},function(){
return "Pulled a rabbit out of a Object";
});
m.pullRabbit(" Str");