2009-10-05 16 views
5

Si tengo varias subclases de algo, y un algoritmo que opera en instancias de esas subclases, y si el comportamiento del algoritmo varía ligeramente dependiendo de qué subclase particular es una instancia, entonces la forma más común orientada a objetos para hacer esto está usando métodos virtuales.Cuándo utilizar la información de tipo de tiempo de ejecución?

Por ejemplo, si las subclases son nodos DOM, y si el algoritmo debe insertar un nodo secundario, ese algoritmo difiere dependiendo de si el nodo padre es un elemento DOM (que puede tener hijos) o DOM (que puede ' t): así que el método insertChildren puede ser virtual (o abstracto) en la clase base DomNode, e implementado de manera diferente en cada una de las subclases DomElement y DomText.

Otra posibilidad es dar a las instancias una propiedad común, cuyo valor se puede leer: por ejemplo, el algoritmo podría leer la propiedad nodeType de la clase base DomNode; o para otro ejemplo, puede tener diferentes tipos (subclases) de paquete de red, que comparten un encabezado de paquete común, y puede leer el encabezado del paquete para ver qué tipo de paquete es.

No he utilizado la información de tipo de tiempo de ejecución mucho más, incluyendo:

  • Los is y as palabras clave en C#
  • downcasting
  • El método Object.GetType en la red del punto
  • El operador typeid en C++

Cuando agrego un nuevo algoritmo m, que depende del tipo de subclase, en cambio, tiendo a agregar un nuevo método virtual a la jerarquía de clases.

Mi pregunta es, ¿cuándo es apropiado usar información de tipo de tiempo de ejecución, en lugar de funciones virtuales?

Respuesta

5

Cuando no hay otra alternativa. Los métodos virtuales siempre son preferidos, pero a veces simplemente no se pueden usar. Hay dos razones por las que esto podría suceder, pero la más común es que no tiene el código fuente de las clases con las que desea trabajar o no puede cambiarlas. Esto sucede a menudo cuando trabajas con un sistema heredado o con una biblioteca comercial de código cerrado.

En .NET, también puede ocurrir que tenga que cargar nuevos ensambles sobre la marcha, como complementos, y generalmente no tiene clases base, pero tiene que usar algo como tipado de pato.

+0

¿Cuál es el motivo para decir que RTTI está en desuso, un método de último recurso? – ChrisW

+2

@ChrisW, es más difícil de entender y mucho más lento de ejecutar. No está en desuso, es solo que otros métodos son mejores :) – vava

+0

Hay pocas razones para que sea más lento: el RTTI se puede almacenar en la clase vtable, al igual que un puntero de función virtual. No estoy seguro de por qué es más difícil de entender, porque, de alguna manera, la comprobación de RTTI es más local: por ejemplo, si ves "if (foo es Foo)", entonces sabes lo que se está comprobando, sin ir y mirar las definiciones de las funciones virtuales en varias subclases. – ChrisW

3

En C++, entre algunos otros casos oscuros (que en su mayoría se refieren a opciones de diseño inferiores), RTTI es una forma de implementar el llamado multi methods.

+0

Es 'un' camino, sí; una forma alternativa es usar http://en.wikipedia.org/wiki/Double_dispatch que usa solo funciones virtuales: agregaría una nueva función virtual para cada subclase del tipo de parámetro. – ChrisW

+0

Si miras el enlace que publiqué, verás que, de hecho, conduce a un "despacho múltiple", que es una generalización del envío doble. ':)' – sbi

0

dynamic_cast <>, si no recuerdo mal, depende de RTTI. Algunas interfaces externas oscuras también pueden confiar en RTTI cuando un objeto se pasa a través de un puntero de vacío (por cualquier razón que podría ocurrir).

Dicho esto, no he visto typeof() en la naturaleza en 10 años de trabajo de mantenimiento Pro C++. (Afortunadamente)

+1

typeof se agregará con C++ 0x como la palabra clave decltype (muy útil, IMO).No pertenece a la información del tipo de tiempo de ejecución, es una construcción en tiempo de compilación y una forma de hacer un uso productivo de parte de la información que un compilador de C++ tiene de todos modos, pero actualmente solo se usa en los mensajes de error. – UncleBens

0

Puede consultar el C# más efectivo para un caso en el que la verificación de tipo de tiempo de ejecución es correcta.

Artículo 3.Especializar algoritmos genéricos Usar la comprobación del tipo de tiempo de ejecución

Puede reutilizar fácilmente los genéricos por simplemente especificando los nuevos parámetros de tipo. Una nueva creación de instancias con nuevos parámetros tipo significa un nuevo tipo que tiene funcionalidad similar.

Todo esto es genial, porque se escribe código menos. Sin embargo, a veces ser más genérico significa no tomar ventaja de un algoritmo más específico, pero claramente superior. Las reglas de lenguaje C# tienen esto en cuenta. Todo lo que necesita es que reconozca que su algoritmo puede ser más eficiente cuando los parámetros de tipo tienen mayores capacidades, y luego a escriba ese código específico. Además, creando un segundo tipo genérico que especifica diferentes restricciones no siempre funciona. Las instanciaciones genéricas se basan en el tipo en tiempo de compilación de un objeto y no el tipo de tiempo de ejecución. Si no puede tener eso en cuenta, puede perder posibles eficiencias.

Por ejemplo, supongamos que se escribe una clase que proporciona una enumeración de orden inversa en una secuencia de elementos representados a través IEnumerable < T>. Para enumerarlo al revés, puede iterarlo y copiar elementos en una colección intermedia con acceso de indexador como List < T> y luego enumerar esa colección usando el acceso del indexador al revés. Pero si su IEnumerable original es IList, por qué no aprovecharlo y proporcionar una forma más eficiente (sin copiar a la colección intermedia) para repetir los elementos al revés. Así que, básicamente, es algo especial que podemos aprovechar, pero que sigue ofreciendo el mismo comportamiento (iterar la secuencia hacia atrás).

Pero, en general, debe considerar detenidamente la verificación del tipo de tiempo de ejecución y asegurarse de que no viole el Principio de sustitución de Liskov.

1

Estas construcciones ("es" y "como") son muy familiares para los desarrolladores de Delphi ya que los manejadores de eventos usualmente bajan los objetos a un antecesor común. Por ejemplo, el evento OnClick pasa el único remitente de argurment: TObject independientemente del tipo de objeto, ya sea TButton, TListBox o cualquier otro. Si desea saber algo más sobre este objeto, debe acceder a él a través de "como", pero para evitar una excepción, puede verificarlo con "es" antes. Este downcasting permite el enlace del tipo de diseño de objetos y métodos que no podría ser posible con una estricta verificación de tipo de clase. Imagine que desea hacer lo mismo si el usuario hace clic en Botón o ListBox, pero si nos proporcionan diferentes prototipos de funciones, no sería posible vincularlos al mismo procedimiento.

En un caso más general, un objeto puede llamar a una función que notifica que el objeto, por ejemplo, ha cambiado. Pero de antemano deja al destino la posibilidad de conocerlo "personalmente" (a través de "tal y como es"), pero no necesariamente. Lo hace al pasar self como antepasado más común de todos los objetos (TObject en el caso Delphi)

+0

Sí, los controladores de eventos son un buen ejemplo. En términos más generales, cada vez que su aplicación desea almacenar un puntero a un tipo de aplicación, utilizando un código de marco que no conoce sus tipos de aplicaciones; esto incluye, por ejemplo, el uso de la propiedad 'object Tag' de muchos de los tipos de dot net framework, y el' void * lpParameter' pasado a la función Win32 'CreateThread'. Puede almacenarlo con la suficiente facilidad, pero luego debe abatir cuando lo recupere. – ChrisW

+0

@ChrisW: No creo que puedas 'dynamic_cast' desde un' void * '(al menos no en C++). ¿Cómo sabe el compilador que hay un objeto de tipo polimórfico en la dirección señalada y cuál es el diseño de sus datos internos? – sbi

Cuestiones relacionadas