stringtranslate.com

Doble despacho

En ingeniería de software , el doble envío es una forma especial de envío múltiple y un mecanismo que envía una llamada de función a diferentes funciones concretas dependiendo de los tipos de tiempo de ejecución de 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 de función en el código depende del tipo dinámico de un solo objeto y, por lo tanto, se conocen como llamadas de envío único o, simplemente, llamadas de función virtual .

Dan Ingalls fue el primero en describir cómo utilizar el doble despacho en Smalltalk , llamándolo polimorfismo múltiple . [1]

Descripción general

El problema general abordado es cómo enviar un mensaje a diferentes métodos dependiendo no sólo del receptor sino también de los argumentos.

Para ello, sistemas como CLOS implementan el envío múltiple . El envío doble es otra solución que reduce gradualmente el polimorfismo en sistemas que no admiten el envío múltiple.

Casos de uso

El doble envío es útil en situaciones en las que la elección del cálculo depende de los tipos de tiempo de ejecución de sus argumentos. Por ejemplo, un programador podría utilizar el doble envío en las siguientes situaciones:

Un modismo común

El modismo común, 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 que se asocian con la resolución dinámica de llamadas, generalmente más que en un lenguaje que solo admite el envío de un solo método. En C++ , por ejemplo, una llamada de función dinámica generalmente se resuelve mediante un solo cálculo de desplazamiento , lo que 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 estáticamente el desplazamiento. En un lenguaje que admite el envío 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 instrucciones (en una cantidad que probablemente no sea mayor que el número total de llamadas a la función, que puede no ser muy significativo).

Ejemplo en Ruby

Un caso de uso común es mostrar un objeto en un puerto de pantalla, que puede ser una pantalla, una impresora o algo completamente distinto que aún no existe. Esta es una implementación simple de cómo manejar esos diferentes medios.

clase Rectángulo def display_on ( puerto ) # selecciona el código correcto en función de la clase de 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 fin fin                 

Sería necesario escribir lo mismo para Oval, Triangle y cualquier otro objeto que quiera mostrarse en un medio, y todo se tendría que reescribir si se creara un nuevo tipo de puerto. El problema es 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) correcto para mostrar.

Una solución mucho más limpia y fácil de mantener es 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 fin      clase Oval def display_on ( puerto ) # segundo puerto de despacho . display_oval ( self ) fin fin      clase DisplayPort def display_rectangle ( object ) # código para mostrar un rectángulo en un DisplayPort end def display_oval ( object ) # código para mostrar un óvalo en un DisplayPort end # ... end          clase PrinterPort def display_rectangle ( object ) # código para mostrar un rectángulo en un PrinterPort fin def display_oval ( object ) # código para mostrar un óvalo en un PrinterPort fin # ... fin          

Doble envío en C++

A primera vista, el doble envío 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 " mangling 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 envío dinámico se basa en el tipo del objeto que llama, lo que significa que utiliza funciones virtuales (anulación) en lugar de sobrecarga de funciones y da como resultado una búsqueda en la tabla virtual. Considere el siguiente ejemplo, escrito en C++ , de colisiones en un juego:

clase Nave Espacial {}; clase Nave Espacial Apolo : Nave Espacial pública {};       clase Asteroide { público : virtual void CollideWith ( SpaceShip & ) { std :: cout << "Un asteroide impactó una nave espacial \n " ; } virtual void CollideWith ( ApolloSpacecraft & ) { std :: cout << "Un asteroide impactó una nave espacial Apollo \n " ; } };                  clase ExplodingAsteroid : público Asteroide { público : void CollideWith ( Nave Espacial & ) override { std :: cout << "ExplodingAsteroid golpeó una Nave Espacial \n " ; } void CollideWith ( Nave Espacial Apollo & ) override { std :: cout << "ExplodingAsteroid golpeó una Nave Espacial Apollo \n " ; } };                     

Si usted tiene:

Asteroide elAsteroide ; Nave Espacial laNave Espacial ; Nave Espacial Apolo laNave Espacial Apolo ;   

Entonces, debido a la sobrecarga de funciones,

el Asteroide . Chocar con ( la nave espacial ); el Asteroide . Chocar con ( la nave espacial Apollo ); 

imprimirá, respectivamente, Asteroid hit a SpaceShipy Asteroid hit an ApolloSpacecraft, sin utilizar ningún envío dinámico. Además:

AsteroideExplosivo elAsteroideExplosivo ; elAsteroideExplosivo . ChocarCon ( laNaveEspacial ); elAsteroideExplosivo . ChocarCon ( laNaveEspacialApolo );  

se imprimirá ExplodingAsteroid hit a SpaceShipy ExplodingAsteroid hit an ApolloSpacecraftrespectivamente, nuevamente sin despacho dinámico.

Con una referencia a un Asteroid, se utiliza el envío dinámico y este código:

Asteroide y theAsteroidReference = theExplodingAsteroid ; la referencia de asteroides . Chocar con ( la nave espacial ); la referencia de asteroides . Chocar con ( la nave espacial Apollo );    

Imprime ExplodingAsteroid hit a SpaceShipy ExplodingAsteroid hit an ApolloSpacecraft, nuevamente como se esperaba. Sin embargo, el código siguiente no funciona como se desea:

Nave Espacial y laReferenciaNaveEspacial = laNaveEspacialApolo ; elAsteroide.ColisionaCon ( laReferenciaNaveEspacial ) ; laReferenciaAsteroide.ColisionaCon ( laReferenciaNaveEspacial ) ;   

El comportamiento deseado es vincular estas llamadas a la función que theApolloSpacecraftlas toma como argumento, ya que ese es el tipo instanciado de la variable, lo que significa que el resultado esperado sería Asteroid hit an ApolloSpacecraftand ExplodingAsteroid hit an ApolloSpacecraft. Sin embargo, el resultado es en realidad Asteroid hit a SpaceShipand ExplodingAsteroid hit a SpaceShip. El problema es que, mientras que las funciones virtuales se envían dinámicamente en C++, la sobrecarga de funciones se realiza de forma estática.

El problema descrito anteriormente se puede resolver simulando un doble envío, por ejemplo, utilizando un patrón de visitante . Supongamos que el código existente se extiende de modo que tanto a SpaceShipcomo ApolloSpacecraftse les asigna la función

vacío virtual CollideWith ( Asteroide & inAsteroide ) { inAsteroide . CollideWith ( * this ); }     

Entonces, aunque el ejemplo anterior todavía no funciona correctamente, replantear las llamadas para que la nave espacial sea el agente nos da el comportamiento deseado:

Nave Espacial y laReferenciaNaveEspacial = laNaveEspacialApolo ; Asteroide y laReferenciaAsteroide = elAsteroideQueExplota ; laReferenciaNaveEspacial.ColisionaCon ( elAsteroide ) ; laReferenciaNaveEspacial.ColisionaCon ( laReferenciaAsteroide ) ;      

Imprime Asteroid hit an ApolloSpacecrafty ExplodingAsteroid hit an ApolloSpacecraft, como se esperaba. La clave es que theSpaceShipReference.CollideWith(theAsteroidReference);hace lo siguiente en tiempo de ejecución:

  1. theSpaceShipReferencees una referencia, por lo que C++ busca el método correcto en la tabla virtual. En este caso, llamará a ApolloSpacecraft::CollideWith(Asteroid&).
  2. Dentro de ApolloSpacecraft::CollideWith(Asteroid&), inAsteroidhay una referencia, por lo que inAsteroid.CollideWith(*this)se realizará otra búsqueda en vtable . En este caso, inAsteroides una referencia a un , ExplodingAsteroidpor lo que ExplodingAsteroid::CollideWith(ApolloSpacecraft&)se llamará a .

Doble envío en C#

En C# , al llamar a un método de instancia que acepta un argumento, se puede lograr un envío múltiple sin emplear el patrón visitante. Esto se hace utilizando el polimorfismo tradicional mientras se convierte el argumento a dynamic . [3] El enlazador en tiempo de ejecución elegirá la sobrecarga del método adecuada en tiempo de ejecución. Esta decisión tendrá en cuenta el tipo de tiempo de ejecución de la instancia del objeto (polimorfismo), así como el tipo de tiempo de ejecución del argumento.

Doble envío a Eiffel

El lenguaje de programación Eiffel puede aplicar el concepto de agentes al problema de doble envío. El ejemplo siguiente aplica la construcción del lenguaje de agentes al problema de doble envío.

Consideremos un dominio problemático con varias formas de FORMA y de SUPERFICIE de dibujo sobre la que dibujar una FORMA. Tanto FORMA como SUPERFICIE conocen una función llamada `dibujar' en sí mismas, pero no en la otra. Queremos que los objetos de los dos tipos interactúen de forma covariable entre sí en un doble envío utilizando un patrón de visitante.

El desafío es lograr 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 muestra los resultados de dos objetos visitantes SURFACE que se pasan de forma polimórfica a una lista de objetos SHAPE polimórficos. El patrón de código de visitante solo tiene en cuenta SHAPE y SURFACE de forma genérica y no el tipo específico de ninguno de ellos. 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 covariante altamente flexible entre estas dos clases diferidas y sus descendientes.

Dibuja un POLÍGONO rojo en ETCHASKETCHDibuja un POLÍGONO rojo en GRAFFITI_WALLDibuja un RECTÁNGULO gris en ETCHASKETCHDibuja un RECTÁNGULO gris en GRAFFITI_WALLDibuja un CUADRILÁTERO verde en ETCHASKETCHDibuja un CUADRILÁTERO verde en GRAFFITI_WALLDibuja un PARALELOGRAMO azul en ETCHASKETCHDibuja un PARALELOGRAMO azul en GRAFFITI_WALLDibuja un POLÍGONO amarillo en ETCHASKETCHDibuja un POLÍGONO amarillo en GRAFFITI_WALLDibuja un RECTÁNGULO morado en ETCHASKETCHDibuja un RECTÁNGULO morado en GRAFFITI_WALL

Configuración

Antes de analizar FORMA o SUPERFICIE, debemos examinar el uso desacoplado de alto nivel de nuestro doble despacho.

Patrón de visitantes

El patrón de visitante funciona por medio de un objeto visitante que visita los elementos de una estructura de datos (por ejemplo, lista, árbol, etc.) de forma polimórfica, aplicando alguna acción (llamada o agente) contra los objetos de elementos polimórficos en la estructura de destino visitada.

En nuestro ejemplo a continuación, hacemos una lista de objetos FORMA polimórficos, visitando cada uno de ellos con una SUPERFICIE polimórfica y pidiendo que la FORMA se dibuje en la SUPERFICIE.

hacer-- Imprimir formas en superficies.locall_shapes : LISTA_ARREGLA [ FORMA ]  l_surfaces : LISTA_ARREGADA [ SUPERFICIE ]  hacercrear l_shapes .make ( 6 )  l_shapes . extend ( create { POLYGON }. make_with_color ( "rojo" ))   l_shapes . extend ( create { RECTANGLE }. make_with_color ( "gris" ))   l_shapes . extend ( create { CUADRILÁTERO } . make_with_color ( "verde" ))   l_shapes . extend ( create { PARALELOGRAMO } . make_with_color ( "azul" ))   l_shapes . extend ( create { POLYGON }. make_with_color ( "amarillo" ))   l_shapes . extend ( create { RECTANGLE }. make_with_color ( "púrpura" ))   crear l_superficies .make ( 2 )  l_surfaces . extend ( crear { ETCHASKETCH }. make )  l_surfaces . extend ( crear { GRAFFITI_WALL }. make )  a través de l_shapes como bucle ic_shapes    a través de l_surfaces como bucle ic_surfaces    ic_surfaces .item .drawing_agent ( ic_shapes .item .drawing_data_agent ) finfinfin

Comenzamos creando una colección de objetos SHAPE y SURFACE. Luego, iteramos sobre una de las listas (SHAPE), lo que permite que los elementos de la otra (SURFACE) visiten cada una de ellas por turno. En el código de ejemplo anterior, los objetos SURFACE visitan objetos SHAPE.

El código realiza una llamada polimórfica a {SURFACE}.draw indirectamente a través de `drawing_agent', que es la primera llamada (despacho) del patrón de doble envío. Pasa un agente indirecto y polimórfico (`drawing_data_agent'), lo que permite que nuestro código de visitante solo conozca dos cosas:

Dado que tanto SURFACE como SHAPE definen sus propios agentes, nuestro código de visitante no tiene que saber cuál es la llamada adecuada que debe realizar, ya sea de forma polimórfica o de otro modo. Este nivel de indirección y desacoplamiento simplemente no se puede lograr en otros lenguajes comunes como C, C++ y Java, excepto a través de alguna forma de reflexión o sobrecarga de características con coincidencia de firmas.

SUPERFICIE

Dentro de la llamada polimórfica a {SURFACE}.draw se encuentra la llamada a un agente, que se convierte en la segunda llamada o despacho polimórfico en el patrón de doble despacho.

clase diferida SUPERFICIEcaracterística { NONE } - Inicialización  hacer-- Inicializar actual.haceragente_de_dibujo := agente de dibujo   fincaracterística -- Acceso agente_de_dibujo : PROCEDIMIENTO [ CUALQUIERA , TUPLA [ CADENA , CADENA ]]     --Agente de dibujo de Corriente.característica { NINGUNA } - Implementación  dibujar ( a_data_agent : FUNCIÓN [ CUALQUIERA , TUPLA , TUPLA [ nombre , color : CADENA ]] )        -- Dibuje 'a_shape' en Actual.locall_result : TUPLE [ nombre , color : CADENA ]    hacerl_result := a_data_agent ( Vacío )   imprimir ( "dibuja un " + l_resultado . color + " " + l_resultado . nombre + " en " + tipo + "%N" )             fintipo : CADENA -- Escriba el nombre del actual.fin diferido fin

El argumento del agente en la línea n.° 19 y la llamada en la línea n.° 24 son polimórficos y desacoplados. El agente está desacoplado porque la función {SURFACE}.draw no tiene idea de en qué clase se basa `a_data_agent'. No hay forma de saber de qué clase se derivó el agente de operaciones, por lo que no tiene que provenir de SHAPE o uno de sus descendientes. Esta es una ventaja distintiva de los agentes Eiffel sobre la herencia única, la vinculación dinámica y polimórfica 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 lo necesita, de forma dinámica, donde la versión de la rutina objetivada se determina en ese momento. El único conocimiento fuertemente ligado es el del tipo de resultado de la firma del agente, es decir, una TUPLE con nombre y dos elementos. Sin embargo, este requisito específico se basa en una demanda de la característica que lo encierra (por ejemplo, la línea n.° 25 utiliza los elementos con nombre de la TUPLE para cumplir con la característica de "dibujar" de SURFACE), que es necesaria y no se ha evitado (y tal vez no se pueda evitar).

Por último, observe cómo solo se exporta la función "drawing_agent" a CUALQUIER cliente. Esto significa que el código de patrón de visitante (que es el ÚNICO cliente de esta clase) solo necesita saber acerca del agente para realizar su trabajo (por ejemplo, usar el agente como la función aplicada a los objetos visitados).

FORMA

La clase SHAPE tiene la base (por ejemplo, los datos del dibujo) de lo que se dibuja, tal vez en una SUPERFICIE, pero no tiene por qué ser así. Nuevamente, los agentes proporcionan la indirección y la independencia de clase necesarias para que la relación covariante con SHAPE sea lo más desacoplada posible.

Además, tenga en cuenta que SHAPE solo proporciona "drawing_data_agent" como una función totalmente exportada a cualquier cliente. Por lo tanto, la única forma de interactuar con SHAPE, además de la creación, es a través de las funciones de "drawing_data_agent", que CUALQUIER cliente utiliza para recopilar datos de dibujo de forma indirecta y polimórfica para SHAPE.

clase diferida FORMAcaracterística { NONE } - Inicialización  make_with_color ( un_color : como el color )   -- Hacer con 'a_color' como 'color'.hacercolor := un_color  agente_datos_de_dibujo := agente datos_de_dibujo   asegurarcolor_set : color . misma_cadena ( un_color )  fincaracterística -- Acceso drawing_data_agent : FUNCIÓN [ CUALQUIERA , TUPLA , como drawing_data ]     --Agente de datos para dibujo.característica { NINGUNA } - Implementación  drawing_data : TUPLE [ nombre : como nombre ; color : como color ]       --Datos necesarios para el dibujo de Corriente.hacerResultado := [ nombre , color ]   finnombre : CADENA --Nombre del objeto actual.fin diferido color : CADENA --Color de la corriente.fin

Ejemplo de nave espacial clásica

Una variación del ejemplo clásico de la nave espacial tiene uno o más objetos de nave espacial que deambulan por un universo lleno de otros elementos, como asteroides rebeldes y estaciones espaciales. Lo que queremos es un método de doble envío para manejar encuentros (por ejemplo, posibles colisiones) entre dos objetos covariantes en nuestro universo imaginario. En nuestro ejemplo a continuación, la excursión de salida de nuestro USS Enterprise y USS Excelsior será:

La nave espacial Enterprise cambia de posición de A-001 a A-002.¡La nave espacial Enterprise toma medidas evasivas para evitar el asteroide "Rogue 1"!La nave espacial Enterprise cambia de posición de A-002 a A-003.¡La nave espacial Enterprise toma medidas evasivas para evitar el asteroide "Rogue 2"!¡La nave espacial Enterprise envía un equipo científico a la nave espacial Excelsior mientras pasan!La nave espacial Enterprise cambia de posición de A-003 a A-004.La nave espacial Excelsior cambia de posición de A-003 a A-005.¡La nave espacial Enterprise toma medidas evasivas para evitar el asteroide "Rogue 3"!La nave espacial Excelsior se encuentra cerca de la estación espacial Deep Space 9 y se puede acoplar.La nave espacial Enterprise cambia de posición de A-004 a A-005.¡La nave espacial Enterprise envía un equipo científico a la nave espacial Excelsior mientras pasan!La nave espacial Enterprise está cerca de la estación espacial Deep Space 9 y se puede acoplar.

Visitante

El visitante del clásico ejemplar de Nave Espacial también dispone de un mecanismo de doble despacho.

hacer-- Permitir que los objetos de la NAVE ESPACIAL visiten y se muevan en un universo.locall_universe : LISTA_ARREGLA [ OBJETO_ESPACIAL ]  l_empresa ,l_excelsior : NAVE ESPACIAL hacercrear l_enterprise .make_with_name ( "Empresa" , "A-001 " )   crear l_excelsior .make_with_name ( "Excelsior" , "A-003 " )   crear l_universe .make ( 0 )  l_universo . fuerza ( l_empresa ) l_universe .force ( crear { ASTEROIDE } .make_with_name ( "Rogue 1" , "A-002" ) )    l_universe .force ( crear { ASTEROIDE } .make_with_name ( "Rogue 2" , "A-003" ) )    l_universo . fuerza ( l_excelsior ) l_universe .force ( crear { ASTEROIDE } .make_with_name ( "Rogue 3" , "A-004" ) )    l_universe .force ( crear { SPACESTATION } .make_with_name ( "Espacio profundo 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ón  visita ( un_objeto : OBJETO_ESPACIAL ; un_universo : LISTA_ARRAYADA [ OBJETO_ESPACIAL ] )     -- 'a_object' visita 'a_universe'.hacera través de un universo como bucle ic_universe    comprobar adjunto { SPACE_OBJECT } ic_universe . elemento como al_universe_object entonces      a_object .encounter_agent .call ( [ al_universe_object .sensor_data_agent ] ) finfinfin

El doble envío se puede ver en la línea n.° 35, donde dos agentes indirectos trabajan juntos para proporcionar dos llamadas covariantes que funcionan en un concierto polimórfico perfecto entre sí. El `a_object' de la función `visit' tiene un `encounter_agent' que se llama con los datos del sensor del `sensor_data_agent' que provienen 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 del visitante

Las únicas características exportadas de un SPACE_OBJECT son los agentes para los datos de encuentro y sensor, 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 del sensor se recopilan y se pasan al objeto visitante en su agente de encuentro. Allí, los datos del sensor de sensor_data_agent (es decir, los elementos de datos de la TUPLE sensor_data devueltos por la consulta sensor_data_agent) se evalúan en relación con el objeto actual y se toma un curso de acción en función de esa evaluación (consulte "encuentro" en SPACE_OBJECT a continuación). Todos los demás datos se exportan a {NONE}. Esto es similar a los ámbitos de C, C++ y Java de Private. Como características no exportadas, los datos y las rutinas solo se utilizan internamente por cada SPACE_OBJECT. Finalmente, tenga en cuenta que las llamadas de encuentro a "print" no incluyen información específica sobre las posibles clases descendientes de SPACE_OBJECT. Lo único que se encuentra en este nivel de la herencia son aspectos relacionales generales basados ​​completamente en lo que se puede saber a partir de los atributos y rutinas de un SPACE_OBJECT general. El hecho de que la salida de la `print' tenga sentido para nosotros, como seres humanos, en base a lo que sabemos o imaginamos sobre naves espaciales, estaciones espaciales y asteroides es mera planificación lógica o coincidencia. El SPACE_OBJECT no está programado con ningún conocimiento específico de sus descendientes.

clase diferida OBJETO ESPACIALcaracterística { NONE } - Inicialización  make_with_name ( a_name : como nombre ; a_position : como posición )       -- Inicializar actual con 'a_name' y 'a_position'. hacer nombre := un_nombre   posición := a_posición   agente_datos_sensor := agente datos_sensor    encuentro_agente := encuentro del agente    asegurar name_set : nombre . misma_cadena ( un_nombre )   position_set : posición . misma_cadena ( una_posición )   fincaracterística -- Acceso agente_de_encuentro : PROCEDIMIENTO [ CUALQUIERA , TUPLA ]    --Agente para la gestión de encuentros con Current.sensor_data_agent : FUNCIÓN [ CUALQUIERA , TUPLA , adjunta como sensor_data_anchor ]       -- Agente para devolver datos del sensor actual.característica -- Configuración set_position ( a_position : como posición )    -- Establezca 'posición' con 'a_position'. hacer imprimir ( tipo + " " + nombre + " cambia la posición de " + posición + " a " + a_posición + ".%N" )                posición := a_posición   asegurar position_set : posición . misma_cadena ( una_posición )   fincaracterística { NINGUNA } - Implementación  encuentro ( a_sensor_agent : FUNCIÓN [ CUALQUIERA , TUPLA , adjunta como sensor_data_anchor ] )        - Detecta e informa sobre el estado de colisión de Current con 'a_radar_agent'. hacer a_sensor_agent .llamada ( [ Vacío ] )  comprobar adjunto { como sensor_data_anchor } a_sensor_agent . last_result como al_sensor_data entonces        si no es nombre . misma_cadena ( al_sensor_data . nombre ) entonces     si ( posición.misma_cadena ( al_sensor_data.posición ) ) entonces    si (( al_sensor_data . is_dockable y is_dockable ) y     ( is_manned y al_sensor_data.is_manned ) y    ( is_manueverable y al_sensor_data . is_not_manueverable )) entonces    imprimir ( tipo + " " + nombre + " está cerca de " + al_sensor_data . tipo + " " +             al_sensor_data .name + " y es acoplable.%N" )   de lo contrario (( is_dockable y al_sensor_data .is_dockable ) y     ( is_manned y al_sensor_data.is_manned ) y    ( is_manueverable y al_sensor_data . is_manueverable )) entonces    print ( type + " " + name + " envía un equipo científico a " + al_sensor_data . type + " " +             al_sensor_data .name + " mientras pasan!%N" )   De lo contrario, si ( is_manned y al_sensor_data .is_not_manned ) entonces     print ( tipo + " " + nombre + " toma una acción evasiva, evitando " +         al_sensor_data . tipo + " `" + al_sensor_data . nombre + "'!%N" )       fin fin fin fin finnombre : CADENA  -- Nombre de la corriente.tipo : CADENA  --Tipo de Corriente. diferido finposición : CADENA  --Posición de la corriente.is_dockable : BOOLEAN  -- ¿Current se puede acoplar a otro objeto tripulado? diferido finis_manned : BOOLEANO  --¿Es Current un objeto tripulado? diferido finis_manueverable : BOOLEAN  --¿Es posible mover la corriente? diferido finsensor_data : adjunto como sensor_data_anchor    --Datos del sensor de corriente. hacer Resultado : = [ nombre , tipo , posición , es_acoplable , no es_acoplable , es_tripulado , no es_tripulado , es_manejable , no es_manejable ]              fin sensor_data_anchor : desmontable TUPLE [ nombre , tipo , posición : CADENA ; es_acoplable , no es_acoplable , está_tripulado , no está_tripulado , es_manejable , no es_manejable : BOOLEAN ]              -- Tipo de datos del sensor ancla de corriente.fin

Hay tres clases descendientes de SPACE_OBJECT:

OBJETO ESPACIAL ASTEROIDE NAVE ESPACIAL ESTACIÓN ESPACIAL

En nuestro ejemplo, la clase ASTEROID se utiliza para los elementos "Rogue", SPACESHIP para las dos naves espaciales 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ón  tipo : STRING = "Nave espacial"    -- <Precursor>is_dockable : BOOLEAN = Verdadero    -- <Precursor>is_manned : BOOLEAN = Verdadero    -- <Precursor>is_manueverable : BOOLEAN = Verdadero    -- <Precursor>fin

Por lo tanto, cualquier NAVE ESPACIAL en nuestro universo puede atracar, ser tripulada y maniobrable. Otros objetos, como los asteroides, no son ninguna de estas cosas. Una ESTACIÓN ESPACIAL, por otro lado, puede atracar y ser tripulada, pero no es maniobrable. Por lo tanto, cuando un objeto tiene un encuentro con otro, primero verifica si las posiciones los colocan en las proximidades uno del otro y, si es así, entonces los objetos interactúan en función de sus propiedades básicas. Tenga en cuenta que los objetos con el mismo tipo y nombre se consideran el mismo objeto, por lo que una interacción está lógicamente deshabilitada.

Ejemplo de conclusión de Eiffel

En lo que respecta al doble envío, Eiffel permite al diseñador y al programador eliminar aún más un nivel de conocimiento directo de objeto a objeto al desacoplar las rutinas de clase de sus clases al convertirlas en agentes y luego pasar esos agentes en lugar de realizar llamadas directas a las características de los objetos. Los agentes también tienen firmas específicas y resultados posibles (en el caso de las consultas), lo que los convierte en vehículos ideales de verificación de tipos estáticos sin renunciar a detalles específicos de los objetos. Los agentes son completamente polimórficos, de modo que el código resultante solo tiene el conocimiento específico necesario para realizar su trabajo local. De lo contrario, no hay una carga de mantenimiento añadida al tener un conocimiento específico de las características de clase internas distribuido 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. Con esto en mente, nunca se debe presumir el uso de agentes en el doble envío y su aplicación en patrones de visitantes. Si se puede ver claramente un límite de diseño en cuanto al dominio de tipos de clase que estarán involucrados en las interacciones covariantes, entonces una llamada directa es la solución más eficiente en términos de gasto computacional. Sin embargo, si se espera que el dominio de clase de los tipos participantes crezca o varíe sustancialmente, entonces los agentes presentan una excelente solución para reducir la carga de mantenimiento en el patrón de doble envío.

Véase también

Referencias

  1. ^ Una técnica sencilla para manejar polimorfismo múltiple. En Proceedings of OOPSLA '86, Object–Oriented Programming Systems, Languages ​​and Applications, páginas 347–349, noviembre de 1986. Impreso como SIGPLAN Notices, 21(11). ISBN  0-89791-204-7
  2. ^ Un C++ más eficaz, por Scott Meyers (Addison-Wesley, 1996)
  3. ^ "Uso de tipo dinámico (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 de método tienen el tipo dinámico ...