delphi oop polymorphism

Reintroducir funciones en Delphi



oop polymorphism (11)

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

Entonces, ¿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 simplemente aceptó esa situación, algún usuario final podría recompilar su componente, ignorar la advertencia. Cosas extrañas sucederían, y te echarían la culpa. Esto requiere que acepten explícitamente que la función no es la misma función.

¿Cuál fue la motivación para tener la palabra clave reintroduce 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 está declarada 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.


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

En otras palabras: le dices al compilador que sabes que ocultas la función antepasado y la reemplazas con esta nueva función y lo haces 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.


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 #.


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

Asumiré 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;

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

¿Por qué solo una advertencia para la función virtual y no la otra? 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;

Imagina que tenemos 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, no importa si el código se compila con la versión 2 o 1 de la biblioteca, en ambos casos se llama a TChild.Setup 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.Setup, 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.

Entonces, el modelo de objetos en Delphi requiere una declaración explícita de 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.


En primer lugar, "reintroducir" rompe la cadena de herencia y no debe usarse, y quiero decir nunca . 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, esta es la manera más simple de hacerlo funcionar:

  1. Tienes como un método virtual en una clase base
  2. Ahora quiere tener un método que tenga 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. Coloca la palabra clave reintroduce allí y su clase base no sabe acerca de su nueva implementación y puede usarla solo al acceder a su objeto desde un tipo de instancia directamente especificado. 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 , solo un estilo terrible: D


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 a la versión original del método virtual incluso en las clases donde lo 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;

Entonces, ¿cuál debería ser el propósito de reintroducir el método virtual que no sea hacer las cosas más difíciles de leer?


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

Si tiene una base de códigos 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 ancestro de su código base, entonces reintroduce le permitirá deshacerse de la advertencia W1010 .

Este es el único lugar donde deberías usar reintroduce .


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, sobre el cual el compilador le advierte.
Reintroduce la desambiguación y le dice al compilador cuál debe usar.
ADescendant.MyMethod llama al TDescendant uno, (ADescendant as TAncestor).MyMethod llama al TAncestor uno. ¡Siempre! Sin confusión ... Compilador feliz!

Esto es cierto si desea que el método descendiente sea virtual o no: en ambos casos, desea romper el vínculo 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 puedes o no quieres 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: conviertes MyMethod en uno estático en el nivel TDescendant y evitas que se sobrescriba. Todas las clases heredadas de TDescendant usarán la implementación TDescendant.

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

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

  1. Al agregar la palabra clave reintroduce solo le dice al compilador que usted sabe que está ocultando ese método y suprime la advertencia. Aún puede usar la palabra clave heredada en su implementación de ese método descendente para llamar al método ancestro.
  2. Si el método del antepasado era virtual o dinámico, entonces puede usar la 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 ancestro, entonces la llamada a ese método seguirá siendo para el método descendente (que luego puede opcionalmente invocar al antepasado por heredado ).

Entonces, la diferencia entre sobrescribir y reintroducir está en el polimorfismo. Con la reintroducción , si convierte el objeto descendiente como el tipo primario, luego llame a ese método obtendrá el método ancestro, pero si accede al tipo descendente entonces obtendrá el comportamiento del descendiente. Con la anulación siempre obtienes el descendiente. Si el método ancestro no fue ni virtual ni dinámico , entonces la reintroducción no se aplica porque ese comportamiento es implícito. (En realidad, podría usar un ayudante de clase, pero no vamos a ir allí ahora).

A pesar de lo que dijo Malach, todavía se puede invocar a heredado en un método reintroducido, incluso si el padre no era ni virtual ni dinámico .

Básicamente reintroducir es como anular , pero funciona con métodos no dinámicos y no virtuales , y no reemplaza el comportamiento si se accede a la instancia del objeto a través de una expresión del tipo ancestro.

Explicación adicional:

Reintroduce es una forma de comunicar intenciones al compilador de que no cometió un error. Anulamos un método en un ancestro con la palabra clave override , pero requiere que el método ancestro sea virtual o dinámico , y que desea que el comportamiento cambie cuando se accede al objeto como la clase antecesora. 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ó).


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 lo uso para constructores ...

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

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

Para los controles visuales, Application.Processmessages se puede llamar 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;


tl; dr: intentar anular un método no virtual no tiene sentido. Agregue la palabra clave reintroduce para reconocer que está cometiendo un error.