Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   OOP (https://www.clubdelphi.com/foros/forumdisplay.php?f=5)
-   -   Necesito llamar a métodos de clases "hija" desde su clase "padre" (https://www.clubdelphi.com/foros/showthread.php?t=40204)

Flecha 09-02-2007 15:06:48

Necesito llamar a métodos de clases "hija" desde su clase "padre"
 
¿Hay alguna manera de llamar a métodos de clases "hija" desde una clase "padre"?

Me explico. Tengo unos cuantos procesos cuya estructura de ejecución es idéntica en todos ellos. O sea, en todos ellos se llama una serie de sub-procesos en el mismo orden.

La diferencia entre esos procesos globales radica en el cuerpo de algunos de sus sub-procesos.

Para simplificar un poco el código he pensado en estructurarlo en clases, de forma que la clase "padre" dicte el orden en el que hay que llamar a cada uno de los sub-procesos, pero que sean los hijos quienes den cuerpo a dichos sub-procesos.

Lo primero que se me ha ocurrido hacer es lo siguiente. Me he declarado en el "padre" un proceso "maestro" que se encargue de llamar en el orden debido a todos los sub-procesos. También en el padre me he declarado como VIRTUAL ABSTRACT todos esos sub-procesos. Y en los hijos es donde me he ido declarando los sub-procesos sobreescribiendo los de el padre.

Yo esperaba que al llamar al proceso "maestro" del padre desde cualquiera de sus hijos, dicho proceso buscaría el cuerpo de los sub-procesos en los hijos. Pero me equivoqué. Cuando se ejecuta el proceso "maestro", éste intenta encontrar el cuerpo de los sub-procedimientos dentro del propio padre, y ahí es donde tengo el problema, porque el cuerpo lo tengo declarado en los hijos. Y como es normal me salta un "abstract error".

Sé perfectamente que lo que intento hacer es una barbaridad y que la lógica dice que desde un "padre" no se puede llamar a métodos de los "hijos". Pero es que es eso exactamente lo que necesito hacer y no se me ocurre ningún truco para conseguirlo.

¿Alguna idea?
Muchas gracias.

seoane 09-02-2007 15:23:17

Pongamos como ejemplo la clase TThread. El método execute de la clase TThread esta declarado de la siguiente manera:
Código Delphi [-]
  procedure Execute; virtual; abstract;
Y cuando creamos una clase descendiente de TThread tenemos que declarar el método Execute de la siguiente manera:
Código Delphi [-]
  procedure Execute; override;

Flecha 09-02-2007 15:38:06

Gracias por la ayuda, pero no me vale.
Es exactamente así como tengo declarados los sub-procedimientos que antes mencioné.
Cita:

Empezado por seoane
Pongamos como ejemplo la clase TThread. El método execute de la clase TThread esta declarado de la siguiente manera:

Código Delphi [-]
procedure Execute; virtual; abstract;




Y cuando creamos una clase descendiente de TThread tenemos que declarar el método Execute de la siguiente manera:

Código Delphi [-]
procedure Execute; override;



Lo que yo necesito es lo siguiente:
Siguiendo el ejemplo que has puesto, supongamos que me creo la clase TThreadHijo que desciende de TThread. Desde TThreadHijo puedo acceder a cualquier método de TThread. Pero lo que yo necesito es ejecutar desde TThreadHijo un método de TThread, y que desde éste a su vez pueda llamar al método Execute (por ejemplo) de TThreadHijo.

Es un poco lioso. Lo siento.
¿Alguna idea de cómo puedo hacerlo?

Muchas gracias.

Ñuño Martínez 09-02-2007 15:43:43

Se me ocurre que tal vez puedas hacerlo con interfaces.

La clase "base" puede llamar procedimientos de la interface. La clase "derivada" simplemente tiene que implementar la interface.

seoane 09-02-2007 16:00:07

Ahora si que me perdí :)

Yo por ejemplo tengo estas 2 clases:
Código Delphi [-]
  TPadre = class
    procedure Vamos;
    procedure Ejecutar; virtual; abstract;
  end;

  THijo = class(TPadre)
    procedure Ejecutar; override;
  end;

// Y las implemento asi
{ TPadre }

procedure TPadre.Vamos;
begin
  Ejecutar;
end;

{ THijo }

procedure THijo.Ejecutar;
begin
  ShowMessage('Hola');
end;

Ahora creamos una instancia de la clase hija y llamamos al procedimiento "Vamos", que pertenece a la clase padre y este a su vez llama al procedimiento "Ejecutar" que esta implementado dentro de la clase hija:
Código Delphi [-]
begin
  with THijo.Create do
  try
    Vamos;
  finally
    Free;
  end;
end;

Según entiendo eso es lo que querías, a menos que lo que quieras es llamar un procedimiento de la clase hija que no este declarado en la clase padre. Eso ya lo veo difícil, de hecho no creo ni que compile. Lo único que se me ocurre es lo siguiente:
Código Delphi [-]
  // Ahora no declaramos el procedimiento del hijo en el padre
  TPadre = class
    procedure Vamos;
  end;

  THijo = class(TPadre)
    procedure Ejecutar;
  end;

// Y en la implementacion hacemos un typecast salvaje
{ TPadre }

procedure TPadre.Vamos;
begin
  THijo(Self).Ejecutar;
end;

{ THijo }

procedure THijo.Ejecutar;
begin
  ShowMessage('Hola');
end;
Esto ultimo no se hasta que punto es correcto, estamos obligando a la clase padre a depender de la hija, esto es contra natura :D

mamcx 09-02-2007 16:07:34

Pues obviamente tienes es un problema de organizacion.

Si un problema es demasiado dificil de resolver, hay que replantear el problema hasta que sea facil.

(Tengo en la punta de la lengua que eso es un patron de diseño pero solo me acuerdo de nombre el singleton y el factory ;))

El problema es que llamas a las cosas padres e hijos. Y como ves, no tenes una herarquia desendente.

Lo que tienes que hacer es llamar a las cosas controladoras, ruteadoras y ejecutoras. O Administradores de flujo, pasos de trabajo. O algo asi (te digo en serio, se me olvidan los nombres academicos de estas cosas!)

En fin... La cosa seria tener una clase Administradora. Lo que haria es simplemente manejar el paso de los flujos de trabajo. Algo como (me mata que no hayas puesto el ejemplo de lo que tu querias hacer en ves de seguir con el ejemplo de los thread!):

TAdminProcesoEnruedado:
procedure Init;
Event Progreso;
ListaAcciones:UnaLista;

Luego La que ejecuta:

TAccionSencilla
procedure Execute(Datos....);

Luego tenes que pensar como fabricarias las cosas al estilo de un diagrama de flujo: Que pasos hay que ejecutar? Cuales SI son suceptibles de jerarquias?

Ñuño tiene una buena idea, usar Interfaces. Pero necesitas organizar las clases.

En fin, de purra intuicion, necesitas unas 3 clases para hacer el trabajo, y seria algo similar a TActionList y TAction.

Segundo, debes buscar eliminar el referenciado fuerte. En vez de pasar las referencias de los objetos pasa parametros. Mientas mas desacoplados esten las cosas mejor.
O si posteas exactamente que es lo que tienes seguro podemos armar una solucione mejor que toda la incoherencia arriba citada !

mamcx 09-02-2007 16:12:58

Ah! Y otra cosa.

Es mejor explicar que es lo que se quiere y dejar a un lado como se ha hecho (o se quiere hacer) eso enrueda las soluciones.

Aunque normalmente aplica al trato con clientes (ej: Cliente dice: Quiero que me hagan un raytracer que funcione con servicios web, cuando lo que quiere es que las *imagenes* de un raytracer se vean en una pagina!) tambien aplica a uno como desarrollador.

Si la solucion no llega de forma natural, es mejor concentrarse en la pregunta. Reformular la pregunta hasta que sea obvia la respuesta.

Flecha 09-02-2007 16:33:22

Muchas gracias de nuevo a todos.

Para quitaros dudas de qué es lo que yo necesitaba exactamente os diré que al final seoane acertó de pleno. :D

He probado lo de la interface y funciona perfectamente. Pero me obliga a declararme estos 3 procedimientos siguientes porque son heredados de la interface base IUnknown.

Código Delphi [-]
type
 IUnknown = interface
   ['{00000000-0000-0000-C000-000000000046}']
   function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;
   function _AddRef: Integer; stdcall;
   function _Release: Integer; stdcall;
 end;

Como tenía que ponerle un cuerpo a estos procedimientos les he puesto el siguiente cuerpo en mi clase "padre" (prefiero seguir llamándola así para entenderme :p ):

Código Delphi [-]
function TClasePadre.QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;
begin
  inherited;
end;
function TClasePadre._AddRef: Integer; stdcall;
begin
  inherited;
end;
function TClasePadre._Release: Integer; stdcall;
begin
  inherited;
end;

Lo que no sé es si tendría que poner algo más dentro del cuerpo de esos métodos o si así me funcionará sin problemas. La verdad es que hasta ahora no había utilizado interfaces :rolleyes: .
Aunque supongo que no habrá problemas pues es de esperar que el INHERITED que les he puesto llame a los métodos de la clase TObject.

Muchas gracias de nuevo a todos.

mamcx 09-02-2007 18:10:59

Deriva las clases de TInterfacedObject y todo bien.

La implementacion que has hecho no funciona con interfaces porque anulaste el sistema de liberacion de memoria!

roman 09-02-2007 19:22:50

Cita:

Empezado por mamcx
Tengo en la punta de la lengua que eso es un patron de diseño

Supongo que te refieres al Template pattern, perfectamente ejemplificado por seoane.

// Saludos

Flecha 12-02-2007 11:53:39

Cita:

Empezado por mamcx
Deriva las clases de TInterfacedObject y todo bien.

La implementacion que has hecho no funciona con interfaces porque anulaste el sistema de liberacion de memoria!

:eek::confused:
Gracias por el consejo. No conocía la clase TInterfacedObject, y si te soy sincero tampoco sé muy bien para qué son el QueryInterface, _AddRef, y _Release. Además, eso que comentas de anular el sistema de liberación de memoria la verdad es que me asusta un poco.

No obstante, he mirado en la ayuda de Delphi y creo que no me será necesario usar el TInterfacedObject. De todos modos, por si acaso me equivoco en algo, te mostraré con más detalle lo que tengo y si ves algo que creas que esta mal no dudes en corregirme, ¿vale?

Código Delphi [-]
TYPE

   TClaseBase = class
   private
      ...
   protected
      ...
   public
      ...
   end;

   TMiInterface = interface
      procedure Metodo;
      ...
   end;

   TClasePadre = (TClaseBase, TMiInterface)
   private
      function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;
      function _AddRef: Integer; stdcall;
      function _Release: Integer; stdcall;
      ...
   protected
      procedure Metodo; virtual; abstract;
      ...
   public
      procedure Ejecutar;
   end;

   TClaseHijo = (TClasePadre)
   private
      ...
   protected
      procedure Metodo; override;
      ...
   public   
   end;

function TClasePadre.QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;
begin
  inherited;
end;
 
function TClasePadre._AddRef: Integer; stdcall;
begin
  inherited;
end;
 
function TClasePadre._Release: Integer; stdcall;
begin
  inherited;
end;
 
procedure TClasePadre.Ejecutar;
begin
  ...
  Metodo;
  ...
end;

Muy resumidamente, es eso lo que yo tengo hecho. En la ayuda de Delphi he visto que TInterfacedObject está declarada de la siguiente manera:

Código Delphi [-]
type
 TInterfacedObject = class(TObject, IUnknown)
 private
   FRefCount: Integer;
 protected
   function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;
   function _AddRef: Integer; stdcall;
   function _Release: Integer; stdcall;
 public
   property RefCount: Integer read FRefCount;
 end;

Mi clase TClasePadre desciende de TClaseBase (la cual por definición desciende de TObject), y del interface TMiInterface (que desciende de IUnknown). Por tanto entiendo que TClasePadre termina heredando todo lo que necesita de TInterfacedObject, excepto el RefCount.

¿Qué opinas? ¿Necesitaré el RefCount para algo? Cuando compilo, Delphi no me pide que incluya el RefCount dentro de TClasePadre. Y cuando ejecuto, no me aparece ningún mensaje de error ni al crear ni al destruir las clases. ¿Necesitaré incluir RefCount de todos modos? ¿Necesitaré poner algo dentro de los métodos QueryInterface, _AddRef, y _Release aparte del inherited?

Por si te vale de ayuda, estamos hablando de Delphi 3. Muy arcaico, ya lo sé. Pero por desgracia yo no decido mis herramientas de trabajo. :(

Muchas gracias de nuevo.

mamcx 12-02-2007 16:48:42

Puedes usar TInterfaceObject de forma segura, de hecho para ello existe.

_AddRef, _Release y RefCount permiten un "recolector de basura" que funciona mediante el conteo de referencias.

La idea es que cada vez se toca un objeto que lo implementa se "aumenta" la cantidad de referencias y luego cuando se suelta el objeto se "decrementa". Cuando el # de referencias llegue a 0, automaticamente se libera el objeto (esto significa que en la practica no es necesario llamar a .Free).

Lo que pasa es que como sabes Delphi (excepto la version .NET) no es un lenguague con GC incorporado y si no se tiene en cuenta las reglas que hay que seguir termina haciendo uno unas vainas muy mal hechas (ej: No se deben mezclar llamadas a interfaces con las de objetos).

TInterfacedObjet es una clase que por decirlo asi, permite a las clases "normales" (que no manejan recolector de basura por referencia) beneficiarse de las ventajas de las interfaces.

Otra nota, es convencion (aunque no obligatoria) llamar las interfaces asi:

IUno
IDos

Y las clases

TUno
TDos

Flecha 14-02-2007 12:54:57

¿No hay que hacer uso del "Free"?
 
Gracias por la información. ;)
Pero no entiendo eso de que no sería necesario llamar a Free para liberar la memoria usada por el objeto. :confused:

Si tengo la clase siguiente

Código Delphi [-]
TMiClase = class (TInterfaceObject, IMiInterface) ... 
end;


Cuando quiera crearme una instancia de TMiClase tendré que ejecutar algo así

Código Delphi [-]
var MiObjeto : TMiClase;
begin
...
MiObjeto := TMiClase.Create;
...
end;


Y se supone que cuando ya no necesite más MiObjeto y quiera liberar su memoria tendré que hacer uso de esto otro

Código Delphi [-]
MiObjeto.Free;
o
Código Delphi [-]
MiObjeto.Destroy; // aunque siempre se aconseja más el uso de Free
¿Qué has querido decir exactamente al decir que no necesitaría hacer uso del Free?

mamcx 14-02-2007 17:10:41

Cuando una clase esta bajo el "amparo" de las interfaces tienen su manejo automatico de memoria.

Sin embargo, el manejo se vuelve algo enrevesado si no se conocen las reglas de como funciona, asi que mejor sigue dandole free ;)

roman 14-02-2007 19:20:31

No, no. Si no quieres ver "Invalid pointer operation" y similares, nunca liberes un objeto referenciado por una interfaz que descienda de TInterfacedObject.

Vamos a ver: no es que Delphi maneje automáticamente la memoria de las interfaces. Lo que sucede es que cada vez que se hace referencia a una interfaz, el compilador añade una llamada a _AddRef, y cada vez que una referencia sale de alcance (por ejemplo, si se tiene una variable interfaz en un procedimiento y se sale de éste), el compilador añade una llamada a _Release.

Pero lo que hagan _AddRef y _Release depende de la clase que haya implementado la interfaz. TInterfacedObject es quien implementa la recolección automática al mantener un conteo de cuántas referencias hay. Cuando _Release ve que la cuenta llega a cero, automáticamente llama al destructor del objeto. Por ello, si hacemos nosotros mismos el Free, _Release marcará el "Invalid Pointer Operation", pues intentará hacer un Free de un objeto que ya no existe.

Si implementamos interfaces con clases que desciendan de TInterfacedObject, entonces jamás deben mezclarse referencias a un objeto con referencias a la interfaz. De hecho, la idea de las interfaces es nunca manipular directamente el objeto, sino sólo a través de la interfaz.

// Saludos

mamcx 14-02-2007 21:33:45

Tienes toda la razon Roman...

Estaba mezclando las cosas en la cabeza....

Flecha 15-02-2007 16:58:44

Muchisimas gracias de nuevo.
Ahora sí que me ha quedado todo claro.
;)
Ahora entiendo el mensaje de error que había empezado a aparecerme cuando se cerraba la aplicación y que no era capaz de localizar. Era porque yo ejecutaba la llamada a Free cuando yo ya no necesitaba el objeto, y luego al cerrarse el programa, éste intentaba volver a ejecutar esos mismos Free gestionados por el _Release.
:)
No obstante, me gusta ser yo quien tenga el control y no fiarme de lo que automáticamente vaya a hacer el programa. Prefiero ser yo quien llame a Create y a Free en vez de esperar a que lo haga el propio programa. Además, este código fuente puede variar en los proximos meses y, llegado ese momento, tanto yo como cualquiera de mis compañeros de trabajo tendríamos que acordarnos de este detalle del Free "automático" para este caso tan partícular. Demasiado riesgo fiarse de la memoria humana.
Así que, aunque sólo sea por esta vez, no voy utilizar el TInterfacedObject ya que voy a ser yo quien llame al Free, y la liberación de memoria va a quedar garantizada.
:D
De nuevo muchísimas gracias. Me habéis ayudado muchísimo. Sois unos cracks.

Lepe 20-04-2007 01:03:53

Flecha, no te doy la razón al completo, mira este código:
Código Delphi [-]
combo.items.Add('nombre1');
combo.items.text := 'nombre2';

¿qué hay en el combo?.... Hombreeeee solo hay 'nombre2' porque al asignar la propiedad Text se hace internamente un Clear de los items....¿y si no sabes eso?...pues lo tendrás que aprender, así de claro.

Con esto quiero decir, que si usas un objeto y se libera automáticamente, añade un comentario a la línea donde se crea y pon que se destruye solo, así tanto tú como tus compañeros sabéis lo que hacéis. Useasé, os acomodáis a los nuevos conceptos, los usáis y todos tan contentos.

Saludos


La franja horaria es GMT +2. Ahora son las 12:30:16.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi