Double Dispatch
En la ingeniería de software , el
double dispatch es una forma especial de multiple
dispatch , y un mecanismo que envía una llamada de función a diferentes
funciones concretas en función de los tipos de tiempo de ejecución de dos
objetos que participan en la llamada. En la mayoría de los sistemas orientados
a objetos , la función concreta que se llama
desde una llamada de función en el código depende del tipo dinámico de un solo
objeto y, por tanto, que se conoce como dispatch single.
Casos de uso
Double dispatch es útil en situaciones donde la elección de cálculo
depende de los tipos de tiempo de ejecución de sus argumentos. Por ejemplo, un
programador podría utilizar el double dispatch en las siguientes
situaciones:
• Ordenar un
conjunto mixto de objetos: los algoritmos requieren que una lista de objetos se
clasifican en un orden canónico. Decidir si un elemento está antes que otro
elemento requiere el conocimiento de ambos tipos y, posiblemente, algún
subconjunto de los campos.
• Algoritmos
adaptativos de colisión: por lo general requieren que las colisiones entre
objetos diferentes se manejen de diferentes maneras. Un ejemplo típico es en un
entorno de juego en el que la colisión entre una nave espacial y un asteroide
se calcula de manera diferente de la colisión entre una nave espacial y una
estación espacial.
• Sistemas de gestión de
personal: pueden enviar diferentes tipos de puestos de trabajo a
personal diferente.
• Sistemas de control de
eventos : que utilizan tanto el tipo de evento y el tipo del objeto
receptor con el fin de llamar la rutina de manejo de eventos correcta.
Cuando se llama a un método virtual
en un objeto, eso se considera de un solo envío, porque a qué método
real se llama depende del tipo del objeto único.
Para el envío doble, tanto el tipo de
objeto como el tipo de argumento de método único se tienen en cuenta. Esto es
como la resolución de sobrecarga del método, excepto que el tipo de argumento
se determina en tiempo de ejecución en double dispatch en lugar de
estáticamente en tiempo de compilación.
Veremos un ejemplo a continuación:
Supongamos que debemos modelar un juego
de naves espaciales tipo Galaga, donde tenemos distintos tipos de objetos
espaciales como ser Naves, Estaciones y Asteroides.
Estos objetos pueden colisionar
entre si y el resultado de dicha colisión depende particularmente de quienes
son los objetos involucrados y del estado de los mismos.
ü Si una nave colisiona
con un asteroide la nave es destruida (vida = 0) y el asteroide disminuye su
velocidad en 3 unidades.
ü Si una nave colisiona
con una estación a baja velocidad (velocidad < 10), entonces la nave se
detiene (velocidad = 0) y la estación no sufre ningún cambio de estado.
ü Si una nave colisiona
con una estación a alta velocidad (velocidad >= 10), entonces la nave es
destruida (vida = 0) y la estación disminuye su vida en 4 unidades.
Una solución
trivial podría ser que cada objeto tenga un método “collideWith” en el que
se verifique contra quien está chocando y se actúe en base a ello:
Las clases Asteroide y Estación
tendrían métodos análogos.
Si bien esta solución funciona, resulta un poco “rústica” en términos de Orientación a Objetos porque viola algunos principios de diseño, entre ellos viola el principio abierto-cerrado, ya que en caso que aparecer un nuevo tipo de objeto (como por ejemplo Cometa) habría que modificar cada uno de los métodos CollideWith para contemplar los nuevos casos.
Si bien esta solución funciona, resulta un poco “rústica” en términos de Orientación a Objetos porque viola algunos principios de diseño, entre ellos viola el principio abierto-cerrado, ya que en caso que aparecer un nuevo tipo de objeto (como por ejemplo Cometa) habría que modificar cada uno de los métodos CollideWith para contemplar los nuevos casos.
Es aquí donde el
Double Dispatch nos propone una alternativa un poco más elegante. La idea es
que cuando un objeto recibe una colisión, delega la resolución al otro objeto
invocando a un método más concreto como se muestra en el siguiente
fragmento de código:
Esta solución implica que cada clase
tendrá un método collideWith por cada clase con la que pueda
llegar a colisionar. En este caso particular donde tenemos 3 clases, tendremos
9 métodos (dependiendo del comportamiento particular que definamos para
resolver la colisión podríamos tener tal vez algunos métodos menos). En
cierto modo esta solución también viola el principio abierto-cerrado, porque en
caso de aparecer un nuevo tipo de objeto es necesario modificar todas las
clases para agregar un nuevo método que resuelva la colisión aunque a
diferencia de la solución anterior, aquí no modificamos un método existente,
sino que agregamos un nuevo método, lo cual es menos rústico.
Finalmente la solución para esta
situación entra en la categoría que Steve McConnell denomina Table-Driven
methods (capítulo 18 de libro Code Complete). La idea es emular una
tabla de métodos virtuales que es lo que usan algunos lenguajes a bajo nivel
para resolver el late binding. Más concretamente lo que se hace es que
cada objeto tenga una tabla o mejor dicho un diccionario (collisionMap) donde
la clave es la clase contra la cual colisiona y el valor es un closure/lamba
con la lógica a ejecutarse al colisionar contra ese tipo de objeto. Implementar
esto en Smalltalk y/o Ruby es trivial, pero en Java tiene una vuelta de rosca
extra, simplemente por la forma en que se implementan los lambdas en Java.
A la hora de agregar un nuevo tipo de
objeto, esta solución requiere agregar una nueva entrada al collisionMap y
un nuevo método si es que la interacción con este nuevo tipo de objeto es
distinta a las ya existentes. Otro punto interesante de esta solución es la
posibilidad de variar el comportamiento de cada instancia de la misma clase, ya
que el collisionMappuede alterarse en tiempo de ejecución.
Existe otra manera de visualizar la
implementación del double dispatch, combinándolo con el patrón de diseño
Visitor(patrón de diseño de comportamiento), ya que el double dispatch es una
técnica que permite añadir operaciones a las clases sin tener que modificarlas.
La operación a ejecutar depende de la clase de petición (acepta) y del tipo de
los dos receptores (Visitor, Elemento).
Por ejemplo: veamos un fragmento de un
código implementando ambos patrones.
Declaración de la interfaz Elemento:
Declaración de la Clase Uno que se implementa de Elemento:
Declaración de la Clase Tres que se implementa de Elemento:
Declaración de la Clase ArribaVisitante que se implementa de Visitor:
Declaración de la Clase AbajoVisitante que se implementa de Visitor:
En el siguiente video verán la explicación de como se
implementa el Double Dispatch junto al patrón de diseño Visitor:
¿Cual seria la diferencia entre instanciar un objeto usando herencia y usar Double Dispatch?
ResponderBorrarPrimero que nada, la herencia como ya debes saber no es un patrón de diseño, cuando instanciamos un objeto y hacemos uso del Double Dispatch estamos implicitamente usando herencia, ademas, en el Double Dispatch el tipo de objeto y el tipo de argumento o parámetro del método se tienen en cuenta mientras que en herencia no.
BorrarEl Double Dispatch parece ser una solución para las limitaciones de la herencia que hace lo esperado en un lenguaje estático. Sin embargo, se debe tener cuidado con su implementación, ya que podría llevar a la violación de los principios de responsabilidad abierto-cerrado. Por lo que se debe manejar con precaución y tener presente que el Double Dispatch no siempre es la solución.
ResponderBorrarBuenas, ¿Existen otras alternativas al Double Dispatch?
ResponderBorrarBuenas Santiago, si otra alternativa seria utilizando las estructuras de control if-else , pero esto conllevaría a crear nuevas sentencias a medida que declaremos un nuevo objeto, y seria difícil darle mantenimiento al código a medida que este progrese.
BorrarHola chicos! En el ejemplo expuesto explicaron el uso del double dispatch y del visitor. ¿Cómo funciona el Double Dispatch em sí, en el patrón de diseño de visitante?
ResponderBorrarHola.El visitor es quien implementa el double dispatch. Los mensajes orientados a objetos manifiestan rutinariamente el envío único, es decir, la operación que se ejecuta depende del nombre de la solicitud y el tipo de receptor. En el double dispatch, la operación ejecutada depende del nombre de la solicitud y el tipo de DOS receptores (el tipo de Visitante y el tipo de elemento que visita).
BorrarDe acuerdo a los comentarios realizados han estado mencionando el término de herencia, pero no han hecho énfasis en el término de polimorfismo que el patrón de double Dispatch propone simular, este método se comporta de distinta forma de acuerdo no solo al objeto que invoca dicho método sino también al parámetro que este recibe.
ResponderBorrarEs muy interesante el articulo, la forma de como funciona un double dispatch y todos los conceptos que esto implica como el polimorfismo y la herencia. Me gustaría acotar que este patrón de diseño no involucra soluciones , si no que puede representarse como un problema que a partir del patrón visitante proporciona la solución.
ResponderBorrarLos ejemplos expuestos por ustedes muestran al Double Dispatch junto con el patrón "visitor" pero ¿éste puede utilizarse de manera independiente? Y si este no es un patrón de diseño (según lo que dicen en el vídeo) por nos estar dentro de la jerarquía, si pudieran clasificarlo en un tipo de patrón ¿cuál sería y por qué?
ResponderBorrarSi, el double dispatch se puede implementar por sí solo, ya que es un mecanismo para llamar una función dependiendo del tipo del objeto. Cuando decimos que es un patrón de diseño me refiero a que busca la solución de un problema en un contexto dado. En mi opinión personal, de clasificarlo, sería un patrón de diseño de comportamiento, ya que busca soluciones respecto a la interacción y responsabilidad entre clases y objetos.
BorrarEsta información me parece de mucha ayuda ya que de acuerdo a lo mencionado los Double Dispatch pueden generarnos ayuda al momento de resolver ciertas situaciones donde no sólo el resultado dependerá del objeto sino a su vez dependerá de ciertos parámetros mandados en ese mismo mensaje, lo cual será de apoyo al momento de realizar mecanismos que permitan la utilización o implementación de la información enviada a través de un mensaje que va desde el código hacia el usuario final.
ResponderBorrar¿Que desventajas o problemas considerarían ustedes tiene el uso del Double Dispatch?
ResponderBorrarLa desventajas de implementar este diseño seria la duplicación de código y su falta de flexibilidad, ya que por cada objeto que utilizamos como instancia, se tiene que crear un nuevo método que determine su ejecución.
BorrarEl double dispatch, es muchas veces dificil de comprender, pero una vez que se dominca la tecnica puede probar ser una herramienta poderosa.
ResponderBorrar¿Cual es la importancia que tiene implementar el double dispatch y que clases de consecuencias puede traer el no hacerlo?
ResponderBorrarImplementar el doble dispatch nos ayuda a cumplir con un principio de la programación orientada a objetos llamado "Open-Close" y nos dice que una clase debe estar abierta a extensiones, pero cerrada a las modificaciones. El double dispatch no es un diseño que todos los lenguajes soporten en su estructura y no podríamos definir consecuencias generales de no implementarlo pero una consecuencia de no implementarlo seria la Introspección (capacidad para inspeccionar los metadatos de un objeto, como atributos, propiedades, visibilidad) esto aplica en lenguajes fuertemente tipados como por ejemplo Java.
BorrarEl double dispatch engloba varios conceptos que deben ser dominados por el programador, tal como la herencia que fue mencionada por ustedes, pero también el polimorfismo, el enlace estático, y la sobrecarga de métodos. Es por ello, que a mi parecer, su implementación es delicada, y porque además a veces se confunde con el patrón de comportamiento: Strategy/Estrategia, afectando la capacidad de mantenimiento del software.
ResponderBorrarLos patrones de comportamiento se identificación por la comunicación con los demás objetos, en tu ejemplo que elementos relaciona el patron Visitor?
ResponderBorrarEn nuestro ejemplo explicado en el vídeo, Los elementos que se relacionan son las funciones "registrarEstadisticas()" que están dentro de la interface "Visitor" y las Funciones "aceptar" que estan previamente definidas en cada una de las subclases.
Borrarpatrón visitor, y el Double Dispatch como ya comentaron se pueden usar por separado ¿podrían mencionar diferencias entre ambos?
ResponderBorrarAsí es, se pueden usar por separado. Con respecto a las diferencias, en primer lugar, el visitor es un patrón de diseño establecido de comportamiento mientras que el double dispatch no está establecido. El visitor permite definir nuevas operaciones sobre una jerarquía de clases sin modificar las clases sobre las que opera y el double disptch intenta resolver situaciones en las que el comportamiento resultante no depende solamente del objeto que recibe el mensaje sino también de parámetro enviado en ese mensaje. En algunos lenguajes donde el envío doble no es compatible de forma nativa se procede a implementar el patrón de visitante como una forma de concatenar dos (o más) despachos únicos. Además el visitante es quien implementa el double dispatch.
Borrar¿Qué se debe tener en cuenta a la hora de aplicar Double Dispatch? y puede trabajar en conjunto con otros patrones de diseño?
ResponderBorrarAl momento de implementar el double dispatch debemos tener en cuenta que es un mecanismo que envía una llamada en función de los tiempos de ejecución de los objetos que participan en la llamada, además se puede clasificar como polimorfismo múltiple y debo recalcar que es un mecanismo que depende no sólo del receptor, sino también en los argumentos presentes en el mensaje. Esto es como la resolución de sobrecarga del método, excepto que el tipo de argumento se determina en tiempo de ejecución en el double dispatch en lugar de estáticamente en tiempo de compilación. Con respecto a la segunda pregunta, si, el double dispatch se puede implementar junto con otros patrones ya que su función es enviar mensajes a diferentes métodos, en otras palabras, permite añadir operaciones a las clases sin tener que modificarlas.
BorrarEl Double dispatch es de gran utilidad en situaciones en donde la elección de cálculo es dependiente de los tipos de tiempo de ejecución con los que cuenten sus argumentos, además su objetivo es resolver situaciones en las que el comportamiento resultante no depende solamente del objeto que recibe el mensaje sino también de parámetro enviado en ese mensaje.
ResponderBorrarEs un mecanismo que envía una llamada en función de los tiempos de ejecución de los objetos que participan, es un mecanismo que depende del receotir y de los argumentos del mensaje a su vez nos ayuda a cumplir con determinar qué clase debe estar abierta a extensiones y cerradas a modificaciones. Se puede xonxluic que busca soluciones a la interacción entre clase y objeto.
ResponderBorrarAl momento de aplicar los patrones de comportamiento el double dispacth es bastante útil para algunos de sus modelos como el Visitor, su mecanismo es una de las formas que usa para comunicar las funciones a utilizar las distintas clases. Muy interesante para resolver situaciones en las que el comportamiento resultante no depende solamente del objeto que recibe el mensaje sino también de parámetro enviado en ese mensaje.
ResponderBorrar