2008-09-10 17 views
30

¿Cuál fue la motivación para tener la palabra clave reintroduce en Delphi?Reintroducir funciones en Delphi

Si tiene una clase secundaria que contiene una función con el mismo nombre que una función virtual en la clase principal y no se declara con el modificador de modificación, entonces se trata de un error de compilación. Agregar el modificador de reintroducir en tales situaciones corrige el error, pero nunca he comprendido el razonamiento del error de compilación.

+6

La mejor respuesta a esta pregunta es correcta, ¿puedes marcarla de esta manera? – LaKraven

Respuesta

1

Cuando la clase antecesora también tiene un método con el mismo nombre, y no se declara necesariamente virtual, verá una advertencia del compilador (ya que ocultaría este método).

En otras palabras: le dice al compilador que sabe que oculta la función ancestro y la reemplaza con esta nueva función y lo hace deliberadamente.

¿Y por qué harías esto? Si el método es virtual en la clase principal, la única razón es evitar el polimorfismo. Aparte de eso, solo anule y no llame heredado. Pero si el método principal no se declara virtual (y no puede cambiar eso, porque usted no posee el código, por ejemplo), puede heredar de esa clase y dejar que las personas hereden de su clase sin ver una advertencia del compilador.

+0

¿Podría aclarar qué significa "como ocultaría este método y no podría llamar heredado"? – Frank

+0

Apuesto a que hay buenas razones para reintroducir en lugar de anular. Su clase principal puede realizar llamadas al método virtual, pero no desea que estas llamadas lleguen a su método reintroducido. –

+0

@mliesen Si no desea que las llamadas de la clase base lleguen a su método "reintroducido", _simplemente use un nombre de método diferente_. No "resuelves" un *** problema *** trivial rompiendo la cadena de herencia. –

61

Si declara un método en una clase descendiente que tiene el mismo nombre que un método en una clase ancestora, entonces está ocultando ese método ancestro, es decir, si tiene una instancia de esa clase descendiente (a la que se hace referencia como esa clase) entonces no obtendrás el comportamiento del antepasado. Cuando el método del ancestro es virtual o dinámico, el compilador le dará una advertencia.

Ahora usted tiene una de dos opciones para suprimir ese mensaje de advertencia:

  1. Agregando la palabra clave reintroducir simplemente le dice al compilador que sabe que está ocultando ese método y que suprime la advertencia. Aún puede usar la palabra clave heredada dentro de su implementación de ese método descendiente para llamar al método ancestro.
  2. Si el método ancestro era virtuales o dinámica, puede utilizar anulación. Tiene el comportamiento añadido de que si se accede a este objeto descendiente a través de una expresión del tipo antecesor, la llamada a ese método seguirá siendo para el método descendiente (que luego puede llamar opcionalmente al ancestro a través de heredado).

Así diferencia entre anulación y reintroducir es de polimorfismo. Con reintroduce, si lanzas el objeto descendiente como el tipo padre, entonces llama a ese método obtendrás el método ancestro, pero si accedes al tipo descendente entonces obtendrás el comportamiento del descendiente. Con anula siempre obtienes el descendiente. Si el método ancestro no fue virtual ni dinámico, entonces reintroduce no se aplica porque ese comportamiento es implícito. (En realidad, podrías usar un ayudante de clase, pero no iremos allí ahora.)

A pesar de lo que dijo Malach, que todavía puede llamar heredaron en un método reintroducido, aunque el padre no era ni virtuales ni dinámica.

Esencialmente es lo mismo que reintroducir anulación, pero funciona con los no dinámica y no métodos virtuales, y no reemplaza el comportamiento si se accede a la instancia del objeto a través de una expresión de la tipo ancestro

una explicación más detallada:

Reintroducir es una forma de comunicar la intención de que el compilador que no se incurrió en ningún error. Nos reemplazar un método en un antepasado con el anulación de palabras clave, pero requiere que el método sea ancestro virtuales o dinámica, y que desea que el comportamiento a cambiar cuando se accede al objeto como la clase ancestro. Ahora ingrese reintroduce. Le permite decirle al compilador que no ha creado accidentalmente un método con el mismo nombre que un método antepasado virtual o dinámico (que sería molesto si el compilador no le advirtió).

+0

Me ha dicho lo que hace la palabra clave reintroduce. Básicamente, dijiste que significa que una función no es virtual. Sí, pero no agrega ningún modificador virtual/overide/dinámico a una función. Pero, ¿por qué Anders Hejlsberg decidió que era necesario agregar la palabra clave reintroduce al idioma? – Frank

+1

Es bueno tener un compilador que te aleje de posibles errores. Pero podría haber sido reportado como un instinto de "advertencia" de un error. –

4

El RTL utiliza la reintroducción para ocultar constructores heredados. Por ejemplo, TComponent tiene un constructor que toma un argumento. Pero, TObject tiene un constructor sin parámetros. Al RTL le gustaría que use solo el constructor de un argumento de TComponent, y no el constructor sin parámetros heredado de TObject al crear un nuevo TComponent. Entonces usa reintroducir para ocultar el constructor heredado. De esta manera, reintroduce es un poco como declarar un constructor sin parámetros como privado en C#.

+0

No veo 'reintroduce' en' TComponent.Create'. –

+0

@RobKennedy Sí, no reintroduce aquí, mientras que el ejemplo es incorrecto, el principal es. Me pregunto si hay alguna magia de compilación que suprima esta advertencia al heredar de TObject. – Alister

2

Reintroduce le dice al compilador que desea llamar al código definido en este método como un punto de entrada para esta clase y sus descendientes, independientemente de otros métodos con el mismo nombre en la cadena de los antepasados.

Crear un TDescendant.MyMethod crearía una confusión potencial para TDescendants al agregar otro método con el mismo nombre, que el compilador le advierte.
Reintroduce las desambiguaciones y le dice al compilador cuál debe usar.
ADescendant.MyMethod llama al TDescendant, (ADescendant as TAncestor).MyMethod llama al TAncestor uno. ¡Siempre! Sin confusión ... Compilador feliz!

Esto es cierto tanto si desea que el método descendiente sea virtual como si no: en ambos casos, desea romper el enlace natural de la cadena virtual. Y no le impide llamar al código heredado desde el nuevo método.

  1. TDescendant.MyMethod es virtual: ... pero no puede o no quiere usar el enlace.
    • No se puede porque la firma del método es diferente. No tiene otra opción ya que anular es imposible en este caso con el tipo de devolución o los parámetros que no son exactamente iguales.
    • Desea reiniciar un árbol de herencia de esta clase.
  2. TDescendant.MyMethod no es virtual: Convierte MyMethod en uno estático en el nivel TDescendant y evita una mayor anulación. Todas las clases heredadas de TDescendant usarán la implementación TDescendant.
7

Hay un montón de respuestas aquí acerca de por qué un compilador que permite ocultar una función miembro silencio es una mala idea. Pero ningún compilador moderno silenciosamente oculta las funciones de los miembros. Incluso en C++, donde está permitido hacerlo, siempre hay una advertencia al respecto, y eso debería ser suficiente.

¿Por qué requieren "reintroducir"? La razón principal es que este es el tipo de error que realmente puede aparecer por accidente, cuando ya no estás mirando las advertencias del compilador. Por ejemplo, digamos que está heredando de TComponent, y los diseñadores de Delphi agregan una nueva función virtual a TComponent. La mala noticia es que su componente derivado, que escribió hace cinco años y distribuyó a otros, ya tiene una función con ese nombre.

Si el compilador acaba de aceptar esa situación, algún usuario final puede recompilar su componente, ignore la advertencia. Cosas extrañas sucederían, y usted sería culpado. Esto requiere que acepten explícitamente que la función no es la misma función.

3

El propósito del modificador de reintroducción es evitar un error lógico común.

Supongo que es de conocimiento común cómo la palabra clave reintroduce corrige la advertencia y explicará por qué se genera la advertencia y por qué la palabra clave está incluida en el idioma. Considere el código delphi a continuación;

TParent = Class 
Public 
    Procedure Procedure1(I : Integer); Virtual; 
    Procedure Procedure2(I : Integer); 
    Procedure Procedure3(I : Integer); Virtual; 
End; 

TChild = Class(TParent) 
Public 
    Procedure Procedure1(I : Integer); 
    Procedure Procedure2(I : Integer); 
    Procedure Procedure3(I : Integer); Override; 
    Procedure Setup(I : Integer); 
End; 

procedure TParent.Procedure1(I: Integer); 
begin 
    WriteLn('TParent.Procedure1'); 
end; 

procedure TParent.Procedure2(I: Integer); 
begin 
    WriteLn('TParent.Procedure2'); 
end; 

procedure TChild.Procedure1(I: Integer); 
begin 
    WriteLn('TChild.Procedure1'); 
end; 

procedure TChild.Procedure2(I: Integer); 
begin 
    WriteLn('TChild.Procedure2'); 
end; 

procedure TChild.Setup(I : Integer); 
begin 
    WriteLn('TChild.Setup'); 
end; 

Procedure Test; 
Var 
    Child : TChild; 
    Parent : TParent; 
Begin 
    Child := TChild.Create; 
    Child.Procedure1(1); // outputs TChild.Procedure1 
    Child.Procedure2(1); // outputs TChild.Procedure2 

    Parent := Child; 
    Parent.Procedure1(1); // outputs TParent.Procedure1 
    Parent.Procedure2(1); // outputs TParent.Procedure2 
End; 

Dado el código anterior, ambos procedimientos en TParent están ocultos. Decir que están ocultos significa que los procedimientos no pueden invocarse a través del puntero TChild. Compilar el ejemplo de código produce una sola advertencia;

[DCC Warning] Project9.dpr (19): W1010 Método 'Procedure1' esconde método virtual de tipo base 'TParent'

¿Por qué sólo una advertencia para la función virtual y no el otro? Ambos están ocultos.

Una virtud de Delphi es que los diseñadores de bibliotecas pueden lanzar nuevas versiones sin temor a romper la lógica del código de cliente existente. Esto contrasta con Java donde agregar nuevas funciones a una clase padre en una biblioteca está plagado de peligros porque las clases son implícitamente virtuales. Digamos que TParent desde arriba vive en una biblioteca de terceros, y la fabricación de la biblioteca publica la nueva versión a continuación.

// version 2.0 
TParent = Class 
Public 
    Procedure Procedure1(I : Integer); Virtual; 
    Procedure Procedure2(I : Integer); 
    Procedure Procedure3(I : Integer); Virtual; 
    Procedure Setup(I : Integer); Virtual; 
End; 

procedure TParent.Setup(I: Integer); 
begin 
    // important code 
end; 

imaginar que tenían el siguiente código en nuestro código de cliente

Procedure TestClient; 
Var 
    Child : TChild; 
Begin 
    Child := TChild.Create; 
    Child.Setup; 
End; 

Para el cliente que no importa si el código se compila contra la versión 2 o 1 de la biblioteca, en ambos casos TChild. Se llama a la instalación como lo intenta el usuario. Y en la biblioteca;

// library version 2.0 
Procedure TestLibrary(Parent : TParent); 
Begin 
    Parent.Setup; 
End; 

Si se llama a TestLibrary con un parámetro TChild, todo funciona según lo previsto. El diseñador de la biblioteca no tiene conocimiento de TChild.Configuración, y en Delphi esto no les causa ningún daño. La llamada anterior se resuelve correctamente en TParent.Setup.

¿Qué pasaría en una situación equivalente en Java? TestClient funcionaría correctamente según lo previsto. TestLibrary no lo haría. En Java, todas las funciones se suponen virtuales. Parent.Setup se resolvería en TChild.Setup, pero recuerde que cuando se escribió TChild.Setup no tenían conocimiento del futuro TParent.Setup, por lo que ciertamente nunca lo llamarán heredado. Entonces, si el diseñador de la biblioteca intenta llamar a TParent.Setup, no será así, sin importar lo que hagan. Y ciertamente esto podría ser catastrófico.

Así que el modelo de objetos en Delphi requiere una declaración explícita de las funciones virtuales en la cadena de clases secundarias. Un efecto secundario de esto es que es fácil olvidarse de agregar el modificador de anulación en los métodos secundarios. La existencia de la palabra clave Reintroduce es una conveniencia para el programador. Delphi fue diseñado para que el programador sea persuadido gentilmente, mediante la generación de una advertencia, de declarar explícitamente sus intenciones en tales situaciones.

+0

Ha respondido de manera sucinta a su propia pregunta, pero ... ¿Por qué reintroduce una palabra clave y no una opción de compilación cuando su utilidad es tan pequeña? –

+1

Porque cuando * es * útil, es muy útil. Evita que ciertas cosas se dañen por accidente. –

2

En primer lugar, "reintroducir" rompe la cadena de herencia y debe no ser utilizado, y quiero decir nunca jamás . Durante todo el tiempo que trabajé con Delphi (aproximadamente 10 años) me encontré con una cantidad de lugares que usan esta palabra clave y siempre ha sido un error en el diseño.

Con esto en mente aquí es la forma más simple funciona:

  1. Tiene como un método virtual en una clase base
  2. Ahora usted quiere tener un método que tiene exactamente el mismo nombre, pero tal vez una firma diferente Entonces, escribe su método en la clase derivada con el mismo nombre y no compilará porque el contrato no se cumple.
  3. se pone el reintroducir palabra clave en allí y su clase base no se sabe acerca de su nueva aplicación y se puede utilizar solamente al acceder al objeto a partir de un tipo de instancia especificado directamente. Lo que eso significa es que toy no puede simplemente asignar el objeto a una variable de tipo base y llamar a ese método porque no está allí con el contrato roto.

Como dije, es pura maldad y debe evitarse a toda costa (bueno, esa es mi opinión al menos). Es como usar Goto - sólo un terrible estilo: D

+0

'Reintroduce' no rompe la cadena de herencia. Suprime la advertencia * sobre * romper la cadena de herencia. –

+1

Creo que la excepción aquí son los constructores, te permite (re) introducir constructores con parámetros; de lo contrario, no podrías hacerlo sin una advertencia (excepto cuando se hereda de un árbol de clases que no tiene constructores). – Alister

3

tl; dr: Tratando de reemplazar un método no virtual, no tiene sentido. Agregue la palabra clave reintroduce para confirmar que está cometiendo un error.

+2

Intentar anular un método no virtual siempre es un error. Ninguna directiva adicional hará que el compilador cambie de opinión al respecto. –

1

Esto se ha introducido en el lenguaje debido a las versiones de Framework (incluido el VCL).

Si tiene un código base existente y una actualización de un Framework (por ejemplo, porque compró una versión más nueva de Delphi) introdujo un método virtual con el mismo nombre como método en un antecesor de su código base, entonces reintroduce le permitirá deshacerse de W1010 warning.

Este es el único lugar donde debe usar reintroduce.

+0

O, en lugar de ** reintroducir ** para enterrar la cabeza en la arena: simplemente cambie el nombre de su método para que ya no rompa la cadena de herencia. (Sin una herramienta de refactorización, puede usar buscar y reemplazar en caso de apuro). –

+0

@CraigYoung que funciona muy bien en una nueva base de código y puede funcionar en bases de código existentes más pequeñas, pero a menudo plantea enormes dolores de cabeza en un código existente más grande bases –

0

reintroduce le permite declarar un método con el mismo nombre que el ancestro, pero con diferentes parámetros. ¡No tiene nada que ver con errores o errores!

Por ejemplo, a menudo se utilizan para los constructores ...

constructor Create (AOwner : TComponent; AParent : TComponent); reintroduce; 

Esto me permite crear las clases internas de una manera más limpia para los controles complejos, tales como barras de herramientas o calendarios. Normalmente tengo más parámetros que eso. A veces es casi imposible o muy desordenado para crear una clase sin pasar algunos parámetros.

Para los controles visuales, se puede llamar a Application.Processmessages después de Create, que puede ser demasiado tarde para usar estos parámetros.

constructor TClassname.Create (AOwner : TComponent; AParent : TComponent); 
begin 
    inherited Create (AOwner); 
    Parent  := AParent; 
    .. 
end; 
+1

Buena suerte con eso.Lo que estás haciendo es ponerte en una situación en la que la transmisión de componentes VCL ya no se comporta de la manera en que se diseñó; particularmente con subclases de sus componentes. –

1

En primer lugar, como se dijo anteriormente, nunca se debe reintroducir deliberadamente el método virtual. El único uso cuerdo de reintroducir es cuando el autor del antepasado (no usted) agregó un método que entra en conflicto con su descendiente y el cambio de nombre de su método descendiente no es una opción. En segundo lugar, puede llamar fácilmente la versión original del método virtual, incluso en las clases donde se reintrodujo con diferentes parámetros:

type 
    tMyFooClass = class of tMyFoo; 

    tMyFoo = class 
    constructor Create; virtual; 
    end; 

    tMyFooDescendant = class(tMyFoo) 
    constructor Create(a: Integer); reintroduce; 
    end; 


procedure ....... 
var 
    tmp: tMyFooClass; 
begin 
    // Create tMyFooDescendant instance one way 
    tmp := tMyFooDescendant; 
    with tmp.Create do // please note no a: integer argument needed here 
    try 
    { do something } 
    finally 
    free; 
    end; 

    // Create tMyFooDescendant instance the other way 
    with tMyFooDescendant.Create(20) do // a: integer argument IS needed here 
    try 
    { do something } 
    finally 
    free; 
    end; 

así que lo que debe ser el propósito de reintroducir método virtual distinta de hacer las cosas más difíciles de leer?