En ingeniería de software , el despacho doble es una forma especial de despacho múltiple y un mecanismo que asigna una llamada a función a diferentes funciones concretas según los tipos de ejecución de los dos objetos involucrados en la llamada. En la mayoría de los sistemas orientados a objetos , la función concreta que se llama desde una llamada a función en el código depende del tipo dinámico de un solo objeto; por lo tanto, se conocen como llamadas de despacho simple o, simplemente, llamadas a funciones virtuales .
Dan Ingalls fue el primero en describir cómo usar el despacho doble en Smalltalk , llamándolo polimorfismo múltiple . [ 1 ]
Descripción general
El problema general que se aborda es cómo enviar un mensaje a diferentes métodos dependiendo no solo del receptor sino también de los argumentos.
Para ello, sistemas como CLOS implementan el despacho múltiple . El despacho doble es otra solución que reduce gradualmente el polimorfismo en sistemas que no admiten el despacho múltiple.
Casos de uso
El despacho doble es útil en situaciones donde la elección del cálculo depende de los tipos en tiempo de ejecución de sus argumentos. Por ejemplo, un programador podría usar el despacho doble en las siguientes situaciones:
- Ordenar un conjunto mixto de objetos: los algoritmos requieren que una lista de objetos se ordene según un orden canónico. Decidir si un elemento precede a otro requiere conocer ambos tipos y, posiblemente, algún subconjunto de los campos.
- Los algoritmos de colisión adaptativos suelen requerir que las colisiones entre diferentes objetos se gestionen de forma distinta. Un ejemplo típico se encuentra en un entorno de juego, donde la colisión entre una nave espacial y un asteroide se calcula de forma diferente a la colisión entre una nave espacial y una estación espacial. [ 2 ]
- Algoritmos de pintura que requieren que los puntos de intersección de los sprites superpuestos se representen de una manera diferente.
- Los sistemas de gestión de personal pueden asignar diferentes tipos de tareas a diferentes empleados. Un
schedulealgoritmo que recibe un objeto de persona de tipo contable y un objeto de trabajo de tipo ingeniería rechaza la asignación de esa persona a ese trabajo. - Sistemas de gestión de eventos que utilizan tanto el tipo de evento como el tipo de objeto receptor para llamar a la rutina de gestión de eventos correcta.
- En los sistemas de cerraduras y llaves, donde existen muchos tipos de cerraduras y llaves, y cada tipo de llave abre varios tipos de cerraduras, no solo es necesario conocer los tipos de objetos involucrados, sino que el subconjunto de "información sobre una llave en particular que es relevante para determinar si abre una cerradura en particular" varía según el tipo de cerradura.
Un modismo común
La práctica habitual, como en los ejemplos presentados anteriormente, es que la selección del algoritmo apropiado se basa en los tipos de argumentos de la llamada en tiempo de ejecución. Por lo tanto, la llamada está sujeta a todos los costos de rendimiento adicionales habituales asociados con la resolución dinámica de llamadas, generalmente mayores que en un lenguaje que solo admite despacho de un único método. En C++ , por ejemplo, una llamada a función dinámica generalmente se resuelve mediante un único cálculo de desplazamiento , lo cual es posible porque el compilador conoce la ubicación de la función en la tabla de métodos del objeto y, por lo tanto, puede calcular el desplazamiento de forma estática. En un lenguaje que admite despacho doble , esto es ligeramente más costoso, porque el compilador debe generar código para calcular el desplazamiento del método en la tabla de métodos en tiempo de ejecución, lo que aumenta la longitud total de la ruta de instrucción (en una cantidad que probablemente no sea mayor que el número total de llamadas a la función, lo cual puede no ser muy significativo).
Ejemplos
Rubí
Un caso de uso común es mostrar un objeto en un puerto de visualización, que puede ser una pantalla, una impresora o cualquier otro dispositivo que aún no exista. Esta es una implementación básica de cómo manejar esos diferentes medios.
clase Rectángulo def display_on ( puerto ) # selecciona el código correcto según la clase del objeto caso puerto cuando DisplayPort # código para mostrar en DisplayPort cuando PrinterPort # código para mostrar en PrinterPort cuando RemotePort # código para mostrar en RemotePort fin finLo mismo debería escribirse para Oval, Triangle y cualquier otro objeto que desee mostrarse en un medio, y todo tendría que reescribirse si se creara un nuevo tipo de puerto. El problema radica en que existe más de un grado de polimorfismo: uno para enviar el método display_on a un objeto y otro para seleccionar el código (o método) adecuado para su visualización.
Una solución mucho más limpia y fácil de mantener consiste entonces en realizar un segundo envío, esta vez para seleccionar el método correcto para mostrar el objeto en el medio:
clase Rectángulo def display_on ( puerto ) # segundo puerto de despacho . display_rectangle ( self ) fin finclase Oval def display_on ( puerto ) # segundo puerto de despacho . display_oval ( self ) fin finclase DisplayPort def display_rectangle ( objeto ) # código para mostrar un rectángulo en un DisplayPort end def display_oval ( objeto ) # código para mostrar un óvalo en un DisplayPort end # ... endclase PrinterPort def display_rectangle ( objeto ) # código para mostrar un rectángulo en un PrinterPort end def display_oval ( objeto ) # código para mostrar un óvalo en un PrinterPort end # ... endC++
A primera vista, el despacho doble parece ser un resultado natural de la sobrecarga de funciones . La sobrecarga de funciones permite que la función llamada dependa del tipo del argumento. Sin embargo, la sobrecarga de funciones se realiza en tiempo de compilación mediante la " manipulación de nombres ", donde el nombre interno de la función codifica el tipo del argumento. Por ejemplo, una función foo(int)puede llamarse internamente __foo_i y la función foo(double)puede llamarse __foo_d . Por lo tanto, no hay colisión de nombres ni búsqueda en la tabla virtual. Por el contrario, el despacho dinámico se basa en el tipo del objeto que realiza la llamada, lo que significa que utiliza funciones virtuales (sobrescritura) en lugar de sobrecarga de funciones , y sí resulta en una búsqueda en la tabla virtual. Considere el siguiente ejemplo, escrito en C++ , de colisiones en un juego:
clase Nave espacial {}; clase ApolloSpacecraft : público Nave espacial {};clase Asteroide { public : virtual void collideWith ( Spaceship & ) { std :: println ( "Asteroide impactó una nave espacial" ); }virtual void collideWith ( ApolloSpacecraft & ) { std :: println ( "Asteroide impactó una nave espacial Apollo" ); } };clase ExplodingAsteroid : public Asteroid { public : void collideWith ( Spaceship & ) override { std :: println ( "ExplodingAsteroid impactó una nave espacial" ); }void collideWith ( ApolloSpacecraft & ) override { std :: println ( "ExplodingAsteroid impactó una ApolloSpacecraft" ); } };Si tienes:
Asteroide asteroide ; Nave espacial nave espacial ; Nave espacial Apollo nave espacial Apollo ;entonces, debido a la sobrecarga de funciones,
asteroide.colisionaCon ( nave espacial ); asteroide.colisionaCon ( nave espacial Apolo ) ;imprimirán, respectivamente, Asteroid hit a Spaceshipy Asteroid hit an ApolloSpacecraft, sin utilizar ningún despacho dinámico. Además:
Asteroide Explosivo Asteroide Explosivo ; Asteroide Explosivo.ColisionaCon ( nave espacial ); Asteroide Explosivo.ColisionaCon ( nave espacial Apolo ) ;imprimirá ExplodingAsteroid hit a Spaceshipy ExplodingAsteroid hit an ApolloSpacecraftrespectivamente, de nuevo sin despacho dinámico.
Con referencia a un Asteroid, se utiliza despacho dinámico y este código:
Asteroide y asteroidRef = asteroideexplosivo ; asteroidRef.collideWith ( nave espacial ); asteroidRef.collideWith ( nave espacial Apolo ) ;imprime ExplodingAsteroid hit a Spaceshipy ExplodingAsteroid hit an ApolloSpacecraft, de nuevo como se esperaba. Sin embargo, el siguiente código no funciona como se desea:
Nave espacial y referenciaNaveespacial = naveespacialApolo ; asteroide.colisionaCon ( referenciaNaveespacial ) ; referenciaAsteroide.colisionaCon ( referenciaNaveespacial ) ;El comportamiento deseado es vincular estas llamadas a la función que toma apolloSpacecraftcomo argumento, ya que ese es el tipo instanciado de la variable, lo que significa que la salida esperada sería Asteroid hit an ApolloSpacecrafty ExplodingAsteroid hit an ApolloSpacecraft. Sin embargo, la salida es en realidad Asteroid hit a Spaceshipy ExplodingAsteroid hit a Spaceship. El problema es que, si bien las funciones virtuales se despachan dinámicamente en C++, la sobrecarga de funciones se realiza estáticamente.
El problema descrito anteriormente se puede resolver simulando el despacho doble, por ejemplo, utilizando un patrón visitante . Supongamos que el código existente se extiende de modo que tanto Spaceshipcomo ApolloSpacecraftreciban la función
virtual void colisionarCon ( Asteroide & asteroide ) { asteroide.collideWith ( * this ) ; }Entonces, aunque el ejemplo anterior todavía no funciona correctamente, reformular las llamadas de manera que la nave espacial sea el agente nos da el comportamiento deseado:
Nave espacial & naveespacialRef = naveespacialapolo ; Asteroide & asteroideRef = asteroideexplosivo ; naveespacialRef.colisionaCon ( asteroide ) ; naveespacialRef.colisionaCon ( asteroideRef ) ;Imprime Asteroid hit an ApolloSpacecrafty ExplodingAsteroid hit an ApolloSpacecraft, como se esperaba. La clave es que spaceshipRef.collideWith(asteroidRef);hace lo siguiente en tiempo de ejecución:
spaceshipRefes una referencia, por lo que C++ busca el método correcto en la tabla virtual. En este caso, llamará aApolloSpacecraft::collideWith(Asteroid&).- Dentro de
ApolloSpacecraft::collideWith(Asteroid&),asteroidhay una referencia, por lo queasteroid.collideWith(*this)dará como resultado otra búsqueda en la tabla virtual . En este caso,asteroides una referencia a un ,ExplodingAsteroidpor lo queExplodingAsteroid::collideWith(ApolloSpacecraft&)se llamará.
DO#
En C# , al llamar a un método de instancia que acepta un argumento, se puede lograr el despacho múltiple sin emplear el patrón Visitor. Esto se hace usando el polimorfismo tradicional y, además, convirtiendo el argumento a tipo dinámico . [ 3 ] El enlazador en tiempo de ejecución elegirá la sobrecarga de método apropiada en tiempo de ejecución. Esta decisión tendrá en cuenta el tipo en tiempo de ejecución de la instancia del objeto (polimorfismo) y el tipo en tiempo de ejecución del argumento.
Eiffel
El lenguaje de programación Eiffel permite aplicar el concepto de agentes al problema del doble despacho. El siguiente ejemplo aplica la construcción del lenguaje de agentes a dicho problema.
Consideremos un dominio de problemas con diversas formas de SHAPE y de SURFACE sobre las cuales dibujar una SHAPE. Tanto SHAPE como SURFACE conocen una función llamada `draw` en sí mismas, pero no entre sí. Queremos que los objetos de ambos tipos interactúen de forma covariante mediante un doble despacho utilizando un patrón Visitor.
El reto consiste en conseguir que una SUPERFICIE polimórfica dibuje una FORMA polimórfica sobre sí misma.
Producción
El ejemplo de salida que se muestra a continuación ilustra los resultados de dos objetos visitantes SURFACE que se pasan polimórficamente sobre una lista de objetos SHAPE polimórficos. El patrón de código del visitante solo reconoce SHAPE y SURFACE de forma genérica, sin especificar el tipo de cada uno. En cambio, el código se basa en el polimorfismo en tiempo de ejecución y en la mecánica de los agentes para lograr una relación de covarianza altamente flexible entre estas dos clases diferidas y sus descendientes.
dibuja un POLÍGONO rojo en ETCHASKETCH dibuja un POLÍGONO rojo en GRAFFITI_WALL dibuja un RECTÁNGULO gris en ETCHASKETCH dibuja un RECTÁNGULO gris en GRAFFITI_WALL dibuja un CUADRILÁTERO verde en ETCHASKETCH dibuja un CUADRILÁTERO verde en GRAFFITI_WALL dibuja un PARALELOGRAMO azul en ETCHASKETCH dibuja un PARALELOGRAMO azul en GRAFFITI_WALL dibuja un POLÍGONO amarillo en ETCHASKETCH dibuja un POLÍGONO amarillo en GRAFFITI_WALL dibuja un RECTÁNGULO morado en ETCHASKETCH dibuja un RECTÁNGULO morado en GRAFFITI_WALL
Configuración
Antes de analizar SHAPE o SURFACE, debemos examinar el uso desacoplado de alto nivel de nuestro sistema de doble despacho.
Patrón de visitantes
El patrón Visitor funciona mediante un objeto visitante que visita los elementos de una estructura de datos (por ejemplo, una lista, un árbol, etc.) de forma polimórfica, aplicando alguna acción (llamada o agente) sobre los objetos de elementos polimórficos en la estructura de destino visitada.
En el ejemplo que se muestra a continuación, creamos una lista de objetos SHAPE polimórficos, visitando cada uno de ellos con una SUPERFICIE polimórfica y solicitando que la forma se dibuje sobre la SUPERFICIE.
hacer-- Imprimir formas en superficies.locall_shapes : LISTA_ARRAYADA [ FORMA ]l_surfaces : ARRAYED_LIST [ SURFACE ]hacercrear l_shapes.make ( 6 )l_shapes . extend ( create { POLYGON }. make_with_color ( "red" ))l_shapes . extend ( create { RECTANGLE }. make_with_color ( "grey" ))l_shapes . extend ( create { QUADRILATERAL }. make_with_color ( "green" ))l_shapes . extend ( create { PARALLELOGRAM }. make_with_color ( "blue" ))l_shapes . extend ( create { POLYGON }. make_with_color ( "yellow" ))l_shapes . extend ( create { RECTANGLE }. make_with_color ( "purple" ))crear l_surfaces.make ( 2 )l_surfaces . extend ( create { ETCHASKETCH }. make )l_surfaces . extend ( create { GRAFFITI_WALL }. make )bucle a través de l_shapes como ic_shapesa través de l_surfaces como bucle ic_surfacesic_surfaces . item . drawing_agent ( ic_shapes . item . drawing_data_agent )finfinfinComenzamos creando una colección de objetos SHAPE y SURFACE. Luego, iteramos sobre una de las listas (SHAPE), permitiendo que los elementos de la otra (SURFACE) visiten cada una de ellas sucesivamente. En el código de ejemplo anterior, los objetos SURFACE visitan los objetos SHAPE.
El código realiza una llamada polimórfica a {SURFACE}.draw indirectamente a través del `drawing_agent`, que es la primera llamada (despacho) del patrón de doble despacho. Pasa un agente indirecto y polimórfico (`drawing_data_agent`), lo que permite que nuestro código visitante solo conozca dos cosas:
- ¿Cuál es el agente de dibujo de la superficie (por ejemplo, al_surface.drawing_agent en la línea #21)?
- ¿Cuál es el agente de datos de dibujo de la forma (por ejemplo, al_shape.drawing_data_agent en la línea #21)?
Dado que tanto SURFACE como SHAPE definen sus propios agentes, nuestro código visitante no necesita saber cuál es la llamada apropiada, ya sea polimórfica o de otro tipo. Este nivel de indirección y desacoplamiento simplemente no se puede lograr en otros lenguajes comunes como C, C++ y Java, excepto mediante algún tipo de reflexión o sobrecarga de características con coincidencia de firmas.
SUPERFICIE
Dentro de la llamada polimórfica a {SURFACE}.draw está la llamada a un agente, que se convierte en la segunda llamada polimórfica o despacho en el patrón de doble despacho.
clase diferidaSUPERFICIEcaracterística { NINGUNA } -- Inicializaciónhacer-- Inicializar Actual.haceragente_de_dibujo := agente dibujarfinfunción -- Accesoagente_de_dibujo : PROCEDIMIENTO [ CUALQUIERA , TUPLE [ CADENA , CADENA ]]-- Agente de dibujo de Current.característica { NINGUNA } -- Implementacióndibujar ( a_data_agent : FUNCIÓN [ CUALQUIERA , TUPLE , TUPLE [ nombre , color : CADENA ]] )-- Dibuja `a_shape` en Current.locall_resultado : TUPLE [ nombre , color : STRING ]hacerl_resultado := a_data_agent ( Void )print ( "dibujar un " + l_result . color + " " + l_result . name + " en " + type + "%N" )fintipo : CADENA-- Escriba el nombre de Actual.fin diferidofinEl argumento del agente en la línea 19 y la llamada en la línea 24 son polimórficos y están desacoplados. El agente está desacoplado porque la función {SURFACE}.draw desconoce la clase en la que se basa `a_data_agent`. No hay forma de saber de qué clase deriva el agente de operación, por lo que no tiene por qué provenir de SHAPE ni de ninguna de sus clases derivadas. Esta es una clara ventaja de los agentes Eiffel sobre la herencia simple, el enlace dinámico y polimórfico de otros lenguajes.
El agente es dinámicamente polimórfico en tiempo de ejecución porque el objeto se crea en el momento en que se necesita, de forma dinámica, y la versión de la rutina objetotizada se determina en ese momento. El único conocimiento fuertemente ligado es el del tipo de resultado de la firma del agente, es decir, una tupla con nombre de dos elementos. Sin embargo, este requisito específico se basa en una exigencia de la funcionalidad que lo contiene (por ejemplo, la línea n.º 25 utiliza los elementos con nombre de la tupla para cumplir la funcionalidad de dibujo de la superficie), lo cual es necesario y no se ha evitado (y quizás no se pueda evitar).
Finalmente, observe cómo solo la característica `drawing_agent` se exporta a CUALQUIER cliente. Esto significa que el código del patrón visitante (que es el ÚNICO cliente de esta clase) solo necesita conocer al agente para realizar su tarea (por ejemplo, usar el agente como la característica aplicada a los objetos visitados).
FORMA
La clase SHAPE contiene la base (por ejemplo, los datos de dibujo) para lo que se dibuja, tal vez sobre una SUPERFICIE, aunque no necesariamente. De nuevo, los agentes proporcionan la indirección y la independencia de la clase necesarias para que la relación de covarianza con SHAPE sea lo más independiente posible.
Además, tenga en cuenta que SHAPE solo proporciona `drawing_data_agent` como una función totalmente exportable para cualquier cliente. Por lo tanto, la única forma de interactuar con SHAPE, aparte de la creación, es a través de las funcionalidades de `drawing_data_agent`, que cualquier cliente utiliza para recopilar datos de dibujo de forma indirecta y polimórfica para SHAPE.
clase diferidaFORMAcaracterística { NINGUNA } -- Inicializacióncrear_con_color ( a_color : como color )-- Crear con `a_color' como `color'.hacercolor := un_coloragente_datos_dibujo := agente datos_dibujoasegurarcolor_set : color.same_string ( a_color )finfunción -- Accesoagente_datos_dibujo : FUNCIÓN [ CUALQUIERA , TUPLE , como datos_dibujo ]-- Agente de datos para dibujo.característica { NINGUNA } -- Implementacióndatos_de_dibujo : TUPLE [ nombre : como nombre ; color : como color ]-- Datos necesarios para dibujar la corriente.hacerResultado := [ nombre , color ]finnombre : CADENA-- Nombre del objeto actual.fin diferidocolor : CUERDA-- Color de la corriente.finEjemplo clásico de nave espacial
Una variación del ejemplo clásico de nave espacial presenta uno o más objetos espaciales vagando por un universo lleno de otros elementos como asteroides errantes y estaciones espaciales. Lo que buscamos es un método de doble despacho para gestionar encuentros (por ejemplo, posibles colisiones) entre dos objetos covariantes en nuestro universo ficticio. En el ejemplo que se muestra a continuación, la excursión de salida de nuestra USS Enterprise y la USS Excelsior será:
La nave estelar Enterprise cambia de posición, pasando de A-001 a A-002. ¡La nave estelar Enterprise realiza una maniobra evasiva, evitando el asteroide 'Rogue 1'! La nave estelar Enterprise cambia de posición, pasando de A-002 a A-003. ¡La nave estelar Enterprise realiza una maniobra evasiva, evitando el asteroide 'Rogue 2'! ¡La nave estelar Enterprise transporta a un equipo científico a la nave estelar Excelsior mientras pasan! La nave espacial Enterprise cambia de posición, pasando de A-003 a A-004. La nave espacial Excelsior cambia de posición, pasando de la A-003 a la A-005. ¡La nave estelar Enterprise realiza una maniobra evasiva, evitando el asteroide 'Rogue 3'! La nave espacial Excelsior se encuentra cerca de la estación espacial Deep Space 9 y puede acoplarse a ella. La nave espacial Enterprise cambia de posición, pasando de A-004 a A-005. ¡La nave estelar Enterprise transporta a un equipo científico a la nave estelar Excelsior mientras pasan! La nave estelar Enterprise se encuentra cerca de la estación espacial Deep Space 9 y puede acoplarse a ella. Visitante
El visitante del ejemplo clásico de la nave espacial también dispone de un mecanismo de doble envío.
hacer-- Permitir que los objetos SPACESHIP visiten y se muevan por un universo.locall_universo : LISTA_ARRAYADA [ OBJETO_ESPACIAL ]l_empresa ,l_excelsior : NAVE ESPACIALhacercrear l_enterprise.make_with_name ( "Enterprise" , " A-001 " )crear l_excelsior.make_with_name ( "Excelsior" , " A-003 " )crear l_universo.make ( 0 )l_universo . fuerza ( l_empresa )l_universe . force ( create { ASTEROID }. make_with_name ( "Rogue 1" , "A-002" ))l_universe . force ( create { ASTEROID }. make_with_name ( "Rogue 2" , "A-003" ))l_universo . fuerza ( l_excelsior )l_universe . force ( create { ASTEROID }. make_with_name ( "Rogue 3" , "A-004" ))l_universe . force ( create { SPACESTATION }. make_with_name ( "Deep Space 9" , "A-005" ))visitar ( l_enterprise , l_universe )l_enterprise.set_position ( "A - 002 " )visitar ( l_enterprise , l_universe )l_enterprise.set_position ( "A- 003 " )visitar ( l_enterprise , l_universe )l_enterprise.set_position ( "A- 004 " )l_excelsior.set_position ( "A - 005 " )visitar ( l_enterprise , l_universe )visitar ( l_excelsior , l_universe )l_enterprise.set_position ( "A - 005 " )visitar ( l_enterprise , l_universe )fincaracterística { NINGUNA } -- Implementaciónvisita ( a_object : OBJETO_ESPACIAL ; a_universo : LISTA_MATRIZADA [ OBJETO_ESPACIAL ] )-- `a_object' visita `a_universe'.hacera través de un_universo como bucle ic_universoVerifique el elemento adjunto { SPACE_OBJECT } ic_universe . item como al_universe_object entoncesa_object . encounter_agent . call ( [ al_universe_object . sensor_data_agent ] )finfinfinEl doble despacho se puede observar en la línea #35, donde dos agentes indirectos trabajan juntos para proporcionar dos llamadas covariantes que funcionan en perfecta sincronía polimórfica. El `a_object` de la función `visit` tiene un `encounter_agent` que se llama con los datos del sensor del `sensor_data_agent` provenientes del `al_universe_object`. La otra parte interesante de este ejemplo en particular es la clase SPACE_OBJECT y su función `encounter`:
Acción de los visitantes
Las únicas características exportadas de un OBJETO ESPACIAL son los agentes para los datos de encuentro y de los sensores, así como la capacidad de establecer una nueva posición. A medida que un objeto (la nave espacial) visita cada objeto en el universo, los datos de los sensores se recopilan y se pasan al objeto visitante en su agente de encuentro. Allí, los datos de los sensores del agente de datos de sensores (es decir, los elementos de datos de la TUPLE de datos de sensores devuelta por la consulta del agente de datos de sensores) se evalúan con respecto al objeto actual y se toma una decisión en función de dicha evaluación (véase «encuentro» en OBJETO ESPACIAL más adelante). Todos los demás datos se exportan a {NINGUNO}. Esto es similar a los ámbitos privados de C, C++ y Java. Como características no exportadas, los datos y las rutinas solo se utilizan internamente por cada OBJETO ESPACIAL. Por último, tenga en cuenta que las llamadas a «print» en el encuentro no incluyen información específica sobre las posibles clases descendientes de OBJETO ESPACIAL. En este nivel de herencia, lo único que se encuentra son aspectos relacionales generales basados completamente en lo que se puede conocer a partir de los atributos y rutinas de un objeto espacial general. El hecho de que la salida de la instrucción `print` tenga sentido para nosotros, como seres humanos, basándonos en lo que sabemos o imaginamos sobre naves estelares, estaciones espaciales y asteroides, es simplemente una planificación lógica o una coincidencia. El objeto espacial no está programado con ningún conocimiento específico de sus descendientes.
clase diferidaOBJETO_ESPACIALcaracterística { NINGUNA } -- Inicializacióncrear_con_nombre ( a_nombre : como nombre ; a_posición : como posición )-- Inicializar Current con `a_name' y `a_position'.hacernombre := un_nombreposición := posición_aagente_datos_sensor := agente datos_sensorencuentro_agente := encuentro del agenteasegurarconjunto_de_nombres : nombre . misma_cadena ( a_name )position_set : posición . misma_cadena ( a_position )finfunción -- Accesoagente_de_encuentro : PROCEDIMIENTO [ CUALQUIERA , TUPLE ]-- Agente para gestionar encuentros con Current.sensor_data_agent : FUNCIÓN [ CUALQUIERA , TUPLE , adjunta como sensor_data_anchor ]-- Agente para devolver datos del sensor de Current.Función -- Configuraciónestablecer_posición ( a_posición : como posición )-- Establecer `position' con `a_position'.hacerimprimir ( tipo + " " + nombre + " cambia de posición de " + posición + " a " + a_posición + ".%N" )posición := posición_aasegurarposition_set : posición . misma_cadena ( a_position )fincaracterística { NINGUNA } -- Implementaciónencuentro ( a_sensor_agent : FUNCIÓN [ CUALQUIERA , TUPLE , adjunto como sensor_data_anchor ] )-- Detectar e informar sobre el estado de colisión de Current con `a_radar_agent'.hacerun_agente_sensor.llamar ( [ Void ] )comprobar adjunto { como sensor_data_anchor } a_sensor_agent . last_result como al_sensor_data entoncessi no name.same_string ( al_sensor_data.name ) entoncessi ( posición . misma_cadena ( al_sensor_data . posición )) entoncessi (( al_sensor_data . is_dockable y is_dockable ) y( is_manned y al_sensor_data . is_manned ) y( is_maneuverable y al_sensor_data . is_not_maneuverable )) entoncesimprimir ( tipo + " " + nombre + " está cerca de " + al_sensor_data . tipo + " " +al_sensor_data . nombre + " y es acoplable.%N" )elseif (( is_dockable y al_sensor_data . is_dockable ) y( is_manned y al_sensor_data . is_manned ) y( is_maneuverable y al_sensor_data . is_maneuverable )) entoncesimprimir ( tipo + " " + nombre + " hace que un equipo científico llegue a " + al_sensor_data . tipo + " " +al_sensor_data.name + " ¡ a medida que pasan!%N" )elseif ( is_manned y al_sensor_data . is_not_manned ) entoncesimprimir ( tipo + " " + nombre + " toma medidas evasivas, evitando " +al_sensor_data.type + " ` " + al_sensor_data.name + " ' !%N " )finfinfinfinfinnombre : CADENA-- Nombre de la corriente.tipo : CADENA-- Tipo de corriente.diferidofinposición : CADENA-- Posición actual.is_dockable : BOOLEAN-- ¿Es posible acoplar Current con otro objeto tripulado?diferidofinis_manned : BOOLEAN-- ¿Es Current un objeto tripulado?diferidofinis_maneuverable : BOOLEAN-- ¿Es posible mover Current?diferidofinsensor_data : adjunto como sensor_data_anchor-- Datos del sensor de corriente.hacerResultado := [ nombre , tipo , posición , es_acoplable , no es_acoplable , está_tripulado , no está_tripulado , es_maniobrable , no es_maniobrable ]finsensor_data_anchor : TUPLE desmontable [ nombre , tipo , posición : STRING ; is_dockable , is_not_dockable , is_manned , is_not_manned , is_maneuverable , is_not_maneuverable : BOOLEAN ]-- Anclaje de tipo de datos del sensor de corriente.finExisten tres clases descendientes de SPACE_OBJECT:
OBJETO ESPACIAL ASTEROIDE NAVE ESPACIAL ESTACIÓN ESPACIALEn nuestro ejemplo, la clase ASTEROID se usa para los elementos «Rogue», SPACESHIP para las dos naves estelares y SPACESTATION para Deep Space Nine. En cada clase, la única especialización es la configuración de la característica «type» y de ciertas propiedades del objeto. El «name» se proporciona en la rutina de creación, así como la «position». Por ejemplo: A continuación se muestra el ejemplo de SPACESHIP.
claseASTRONAVEheredarOBJETO_ESPACIALcrearhacer_con_nombrecaracterística { NINGUNA } -- Implementacióntipo : CADENA = "Starship"-- <Precursor>is_dockable : BOOLEAN = True-- <Precursor>is_manned : BOOLEAN = True-- <Precursor>is_maneuverable : BOOLEAN = True-- <Precursor>finAsí pues, cualquier nave espacial en nuestro universo puede acoplarse, ser tripulada y maniobrable. Otros objetos, como los asteroides, carecen de estas características. Una estación espacial, en cambio, puede acoplarse y ser tripulada, pero no maniobrable. Por lo tanto, cuando un objeto se encuentra con otro, primero comprueba si sus posiciones los sitúan cerca uno del otro y, de ser así, interactúan en función de sus propiedades básicas. Cabe destacar que los objetos del mismo tipo y nombre se consideran el mismo objeto, por lo que lógicamente no se permite la interacción.
Conclusión
En lo que respecta al despacho doble, Eiffel permite al diseñador y al programador reducir aún más el nivel de conocimiento directo entre objetos al desacoplar las rutinas de clase de sus clases, convirtiéndolas en agentes y pasándoles estos agentes en lugar de realizar llamadas directas a las características de los objetos. Los agentes también tienen firmas específicas y posibles resultados (en el caso de las consultas), lo que los convierte en vehículos ideales para la verificación estática de tipos sin renunciar a detalles específicos de los objetos. Los agentes son totalmente polimórficos, de modo que el código resultante solo posee el conocimiento específico necesario para realizar su tarea local. Por lo demás, no se añade ninguna carga de mantenimiento al tener el conocimiento específico de las características internas de la clase disperso entre muchos objetos covariantes. El uso y la mecánica de los agentes garantizan esto. Una posible desventaja del uso de agentes es que un agente es computacionalmente más costoso que su contraparte de llamada directa. Teniendo esto en cuenta, nunca se debe presuponer el uso de agentes en el despacho doble ni su aplicación en los patrones Visitor. Si se puede identificar claramente un límite de diseño en cuanto al dominio de tipos de clases que participarán en las interacciones covariantes, entonces una llamada directa es la solución más eficiente en términos de costo computacional. Sin embargo, si se prevé que el dominio de clases de los tipos participantes crezca o difiera sustancialmente, entonces los agentes representan una excelente solución para reducir la carga de mantenimiento en el patrón de doble despacho.
Véase también
Referencias
- ↑ Una técnica sencilla para manejar el polimorfismo múltiple. En Actas de OOPSLA '86, Sistemas, lenguajes y aplicaciones de programación orientada a objetos, páginas 347-349, noviembre de 1986. Publicado como SIGPLAN Notices, 21(11). ISBN 0-89791-204-7
- ↑ C++ más eficaz, de Scott Meyers (Addison-Wesley, 1996)
- ↑ "Uso del tipo dynamic (Guía de programación de C#)" . Microsoft Developer Network . Microsoft. 30 de septiembre de 2009. Consultado el 25 de mayo de 2016.
...
La resolución de sobrecarga se produce en tiempo de ejecución en lugar de en tiempo de compilación si uno o más de los argumentos en una llamada a un método tienen el tipo dynamic
...
- patrones de diseño de software
- Método (programación informática)