Sue es el diseño de una biblioteca JavaScript, Magician.js
. Su eje central es una función que extrae Rabbit
el argumento pasado.
Ella sabe que sus usuarios pueden querer sacar un conejo de una String
, una Number
, una Function
, tal vez incluso una HTMLElement
. Con eso en mente, podría diseñar su API de esta manera:
La interfaz estricta
Magician.pullRabbitOutOfString = function(str) //...
Magician.pullRabbitOutOfHTMLElement = function(htmlEl) //...
Cada función en el ejemplo anterior sabría cómo manejar el argumento del tipo especificado en el nombre de la función / nombre del parámetro.
O bien, podría diseñarlo así:
La interfaz "ad hoc"
Magician.pullRabbit = function(anything) //...
pullRabbit
tendría que explicar la variedad de diferentes tipos esperados que anything
podría ser el argumento, así como (por supuesto) un tipo inesperado:
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.
};
El primero (estricto) parece más explícito, quizás más seguro y quizás más eficaz, ya que hay pocos o ningún gasto general para la verificación o conversión de tipos. Pero este último (ad hoc) se siente más simple al mirarlo desde afuera, ya que "simplemente funciona" con cualquier argumento que el consumidor de API considere conveniente pasarle.
Para la respuesta a esta pregunta , me gustaría ver los pros y los contras específicos de cada enfoque (o de un enfoque completamente diferente, si ninguno de los dos es ideal), que Sue debe saber qué enfoque tomar al diseñar la API de su biblioteca.
fuente
Respuestas:
Algunos pros y contras
Pros para polimórficos:
Pros para ad-hoc:
Cat
instancia? ¿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.pullRabbitOutOfString
es 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:
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:
Puedo hacer tales
makePullable
funciones para otros objetos, podría crear unmakePullableString
, 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 unpullOfHat
método, puedo extraerlo con el método del mago.Entonces Magician podría hacer:
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:
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:
fuente
El problema es que está tratando de implementar un tipo de polimorfismo que no existe en JavaScript: JavaScript es casi universalmente mejor tratado como un lenguaje de tipo pato, a pesar de que admite algunas facultades de tipo.
Para crear la mejor API, la respuesta es que debe implementar ambos. Es un poco más de tipeo, pero a la larga ahorrará mucho trabajo para los usuarios de su API.
pullRabbit
debería ser un método de árbitro que verifica los tipos y llama a la función apropiada asociada con ese tipo de objeto (por ejemplopullRabbitOutOfHtmlElement
).De esa manera, mientras que los usuarios de prototipos pueden usar
pullRabbit
, pero si notan una desaceleración, pueden implementar la verificación de tipo en su extremo (probablemente de una manera más rápida) y simplemente llamarpullRabbitOutOfHtmlElement
directamente.fuente
Esto es JavaScript A medida que lo mejore, encontrará que a menudo hay un camino intermedio que ayuda a negar dilemas como este. Además, realmente no importa si un 'tipo' no compatible es atrapado por algo o se rompe cuando alguien intenta usarlo porque no hay compilación frente al tiempo de ejecución. Si lo usas mal, se rompe. Intentar ocultar que se rompió o hacer que funcione a mitad de camino cuando se rompió no cambia el hecho de que algo está roto.
Así que tenga su pastel y cómelo también y aprenda a evitar la confusión de tipos y las roturas innecesarias manteniendo todo muy, muy obvio, como bien nombrado y con todos los detalles correctos en los lugares correctos.
En primer lugar, recomiendo adquirir el hábito de poner a sus patos en fila antes de que necesite verificar los tipos. Lo más ágil y eficiente (pero no siempre lo mejor en lo que respecta a los constructores nativos) sería golpear primero los prototipos para que su método ni siquiera tenga que preocuparse por el tipo compatible que está en juego.
Nota: Se considera una mala forma hacer esto a Object ya que todo hereda de Object. Yo personalmente evitaría la función también. Algunos pueden sentirse ansiosos por tocar el prototipo de cualquier constructor nativo, lo que podría no ser una mala política, pero el ejemplo aún podría servir cuando trabaje con sus propios constructores de objetos.
No me preocuparía este enfoque para un método de uso tan específico que no es probable que golpee algo de otra biblioteca en una aplicación menos complicada, pero es un buen instinto para evitar afirmar algo demasiado general en los métodos nativos en JavaScript si no lo hace tiene que hacerlo a menos que esté normalizando métodos más nuevos en navegadores desactualizados.
Afortunadamente, siempre puede preasignar tipos o nombres de constructores a métodos (tenga cuidado con IE <= 8 que no tiene <objeto> .constructor.name que requiere que lo analice fuera de los resultados de toString de la propiedad del constructor). Todavía está en efecto comprobando el nombre del constructor (typeof es un poco inútil en JS al comparar objetos) pero al menos se lee mucho mejor que una declaración de interruptor gigante o si / de lo contrario encadena en cada llamada del método a lo que podría ser un ancho variedad de objetos
O usando el mismo enfoque de mapa, si desea acceder al componente 'this' de los diferentes tipos de objetos para usarlos como si fueran métodos sin tocar sus prototipos heredados:
Un buen principio general en cualquier idioma de la OMI es tratar de ordenar detalles de ramificación como este antes de llegar al código que realmente aprieta el gatillo. De esa manera, es fácil ver a todos los jugadores involucrados en ese nivel superior de API para una buena visión general, pero también es mucho más fácil determinar dónde se encontrarán los detalles que podrían interesarle a alguien.
Nota: todo esto no se ha probado, porque supongo que nadie realmente tiene un uso de RL. Estoy seguro de que hay errores tipográficos / errores.
fuente
Esta (para mí) es una pregunta interesante y complicada de responder. De hecho, me gusta esta pregunta, así que haré todo lo posible para responder. Si realiza alguna investigación sobre los estándares para la programación javascript, encontrará tantas formas "correctas" de hacerlo como personas que promocionan la forma "correcta" de hacerlo.
Pero como estás buscando una opinión sobre cuál es la mejor manera. Aquí va nada.
Personalmente, preferiría el enfoque de diseño "ad hoc". Viniendo de un fondo c ++ / C #, este es más mi estilo de desarrollo. Puede crear una solicitud pullRabbit y hacer que ese tipo de solicitud verifique el argumento pasado y haga algo. Esto significa que no tiene que preocuparse sobre qué tipo de argumento se pasa en ningún momento. Si usa el enfoque estricto, aún necesitaría verificar de qué tipo es la variable, pero lo haría antes de realizar la llamada al método. Entonces, al final, la pregunta es, ¿desea verificar el tipo antes de hacer la llamada o después?
Espero que esto ayude, no dude en hacer más preguntas en relación con esta respuesta, haré todo lo posible para aclarar mi posición.
fuente
Cuando escribe, Magician.pullRabbitOutOfInt, documenta lo que pensaba cuando escribió el método. La persona que llama esperará que esto funcione si pasa cualquier número entero. Cuando escribes, Magician.pullRabbitOutOfAnything, la persona que llama no sabe qué pensar y tiene que buscar su código y experimentar. Puede funcionar para un Int, pero ¿funcionará para un Long? Un flotador? ¿Un doble? Si está escribiendo este código, ¿hasta dónde está dispuesto a llegar? ¿Qué tipo de argumentos estás dispuesto a apoyar?
La ambigüedad lleva tiempo para comprender. Ni siquiera estoy convencido de que sea más rápido escribir:
Vs:
OK, así que agregué una excepción a su código (que recomiendo) para decirle a la persona que llama que nunca imaginó que le pasarían lo que hicieron. Pero escribir métodos específicos (no sé si JavaScript le permite hacer esto) no es más código y es mucho más fácil de entender como quien llama. Establece suposiciones realistas sobre lo que pensó el autor de este código, y hace que el código sea fácil de usar.
fuente