Algunos pros y contras
Pros para polimórficos:
- Una interfaz polimórfica más pequeña es más fácil de leer. Solo tengo que recordar un método.
- Va con la forma en que se debe usar el idioma: escribir en pato.
- Si está claro de qué objetos quiero sacar un conejo, no debería haber ambigüedad de todos modos.
- Hacer muchas verificaciones de tipos se considera malo incluso en lenguajes estáticos como Java, donde tener muchas verificaciones de tipos para el tipo de objeto crea un código feo, si el mago realmente necesita diferenciar entre el tipo de objetos de los que está sacando un conejo. ?
Pros para ad-hoc:
- Es menos explícito, ¿puedo extraer una cadena de una
Catinstancia? ¿Eso solo funcionaría? si no, ¿cuál es el comportamiento? Si no limito el tipo aquí, tengo que hacerlo en la documentación o en las pruebas que podrían hacer un contrato peor.
- Tienes todo el manejo de tirar de un conejo en un solo lugar, el mago (algunos podrían considerar esto una estafa)
- Los optimizadores JS modernos diferencian entre funciones monomórficas (funciona en un solo tipo) y polimórficas. Saben cómo optimizar los monomórficos mucho mejor, por lo que
pullRabbitOutOfStringes probable que la versión sea mucho más rápida en motores como V8. Vea este video para más información. Editar: yo mismo escribí un perf, resulta que en la práctica, este no es siempre el caso .
Algunas soluciones alternativas:
En mi opinión, este tipo de diseño no es muy 'Java-Scripty' para empezar. JavaScript es un lenguaje diferente con idiomas diferentes de lenguajes como C #, Java o Python. Estos modismos se originan en años de desarrolladores que intentan comprender las partes débiles y fuertes del lenguaje, lo que haría es tratar de mantener estos modismos.
Hay dos buenas soluciones en las que puedo pensar:
- Elevar objetos, hacer que los objetos sean "extraíbles", hacer que se ajusten a una interfaz en tiempo de ejecución, y luego hacer que el mago trabaje en objetos extraíbles.
- Usando el patrón de estrategia, enseñando al mago dinámicamente cómo manejar diferentes tipos de objetos.
Solución 1: elevar objetos
Una solución común a este problema es 'elevar' los objetos con la capacidad de sacarles conejos.
Es decir, tener una función que tome algún tipo de objeto y agregue sacarlo de un sombrero para ello. Algo como:
function makePullable(obj){
obj.pullOfHat = function(){
return new Rabbit(obj.toString());
}
}
Puedo hacer tales makePullablefunciones para otros objetos, podría crear un makePullableString, etc. Estoy definiendo la conversión en cada tipo. Sin embargo, después de elevar mis objetos, no tengo ningún tipo para usarlos de forma genérica. Una interfaz en JavaScript está determinada por un tipeo de pato, si tiene un pullOfHatmétodo, puedo extraerlo con el método del mago.
Entonces Magician podría hacer:
Magician.pullRabbit = function(pullable) {
var rabbit = obj.pullOfHat();
return {rabbit:rabbit,text:"Tada, I pulled a rabbit out of "+pullable};
}
Elevar objetos, usando algún tipo de patrón de mezcla parece ser la cosa más JS que hacer. (Tenga en cuenta que esto es problemático con los tipos de valores en el lenguaje que son cadena, número, nulo, indefinido y booleano, pero todos pueden encuadrarse)
Aquí hay un ejemplo de cómo se vería ese código
Solución 2: Patrón de estrategia
Al discutir esta pregunta en la sala de chat de JS en StackOverflow, mi amigo phenomnomnominal sugirió el uso del patrón de Estrategia .
Esto le permitiría agregar las habilidades para extraer conejos de varios objetos en tiempo de ejecución y crearía un código muy JavaScript. Un mago puede aprender a sacar objetos de diferentes tipos de sombreros, y los saca en base a ese conocimiento.
Así es como podría verse esto en 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
Puede ver el código JS equivalente aquí .
De esta manera, te beneficias de ambos mundos, la acción de cómo tirar no está estrechamente acoplada ni a los objetos ni al Mago, y creo que esto es una solución muy buena.
El uso sería algo como:
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");