domingo, 8 de diciembre de 2019

Double Dispatch

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.

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 Dos 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: 


27 comentarios:

  1. ¿Cual seria la diferencia entre instanciar un objeto usando herencia y usar Double Dispatch?

    ResponderBorrar
    Respuestas
    1. Primero 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.

      Borrar
  2. El 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.

    ResponderBorrar
  3. Buenas, ¿Existen otras alternativas al Double Dispatch?

    ResponderBorrar
    Respuestas
    1. Buenas 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.

      Borrar
  4. Hola 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?

    ResponderBorrar
    Respuestas
    1. Hola.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).

      Borrar
  5. De 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.

    ResponderBorrar
  6. Es 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.

    ResponderBorrar
  7. Los 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é?

    ResponderBorrar
    Respuestas
    1. Si, 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.

      Borrar
  8. Esta 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
  9. ¿Que desventajas o problemas considerarían ustedes tiene el uso del Double Dispatch?

    ResponderBorrar
    Respuestas
    1. La 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.

      Borrar
  10. El double dispatch, es muchas veces dificil de comprender, pero una vez que se dominca la tecnica puede probar ser una herramienta poderosa.

    ResponderBorrar
  11. ¿Cual es la importancia que tiene implementar el double dispatch y que clases de consecuencias puede traer el no hacerlo?

    ResponderBorrar
    Respuestas
    1. Implementar 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.

      Borrar
  12. El 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.

    ResponderBorrar
  13. Los patrones de comportamiento se identificación por la comunicación con los demás objetos, en tu ejemplo que elementos relaciona el patron Visitor?

    ResponderBorrar
    Respuestas
    1. En 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.

      Borrar
  14. patrón visitor, y el Double Dispatch como ya comentaron se pueden usar por separado ¿podrían mencionar diferencias entre ambos?

    ResponderBorrar
    Respuestas
    1. Así 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
  15. ¿Qué se debe tener en cuenta a la hora de aplicar Double Dispatch? y puede trabajar en conjunto con otros patrones de diseño?

    ResponderBorrar
    Respuestas
    1. Al 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.

      Borrar
  16. El 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.

    ResponderBorrar
  17. Es 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.

    ResponderBorrar
  18. Al 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