Anoche, era una de esas noches en las que uno acaba por hacer de todo…, quiero decir de todo menos lo que se tiene que hacer… El calor de este final de Julio y la humedad, me invitaron finalmente a salirme a la terraza, junto a los geranios y la enredadera, buscando un poco de aire fresco, y como habían pocas o ninguna ganas de trabajar, me puse a ojear los enlaces que figuran en el panel situado a la derecha del blog, cayendo por casualidad en la web de Neftalí.

Pasé un buen rato recorriendo las secciones pero sin ningun destino concreto hasta que mi vista se detuvo, por unos segundos, en uno de los articulos de Neftalí (German Estevez), que hablaba sobre la ejecución de metodos a través del nombre del mismo. Es un artículo que tiene algo de tiempo, pero independientemente de estó, como en otros muchos articulos, pueden desprenderse algunas reflexiones sobre puntos básicos, que con toda seguridad resultan curiosos e interesantes, no solo para la persona que se inicia, ya que son comunes a todas las versiones de Delphi. Al menos a mi, siempre me fascinó el halo de misterio que rodea al polimorfismo, a lo que pueda oler a RTTI que siempre acaban siendo punteros y direcciones de memoria misteriosas… 🙂

Lo siguiente que hice fue abrir el entorno de Delphi. Efectivamente no tenía ganas de trabajar. 🙂
¿Quien puede tener ganas de trabajar en una noche así?

¿Qué pensaba…?

Os confieso que estaba dandole vueltas a la cabeza sobre cómo podría sacar alguna utilidad de lo leido en el artículo, si es que realmente me valía para algo. De hecho, en el framework que estamos viendo en el Boletin, nos acercamos a conceptos similares cuando registramos una clase, y era el nombre de la clase, el que nos valia por ejemplo para gestionar su creación a traves de una metaclase especializada para tal fin.

O bien era una de esas cosas que se leen y que formalmente no sabes sacarle la utilidad.

Así que empecé a contar una historia con el único fin de pensar unos minutos sobre todo esto:…. “Erase una vez una granja

TGranja = class(TComponent)
end;

y un montón de animales que iban a vivir en ella. (Dibujemos los animales).

TAnimal = class(TComponent)
end;

Mi hijo me miraba como diciendo: ¡este cada vez está peor!

Ya estamos acabando…: Nos faltan las especies concretas.

Podemos tener un caballo, una vaca, quizas varios cerdos y cientos de gallinas. La propuesta es que no dejen de ser animales… 🙂

TVaca = class(TAnimal)
end;

Tras un buen rato tecleando acabé completando mi idea de Granja y de Animal, representados en las dos clases correspondientes. La clase TGranja basicamente se encargaba de gestionar la entrada de animales a la misma, manteniendo internamente una lista de las distintas especies y de cada uno de los ejemplares. Además, disponía como cualquier casa o empresa de un presupuesto, que mermaría en cada compra de animales asi como cada vez que los alimentaban. Sin dinero no se pueden comprar animales, ¿no?.

Veamos como ha quedado…

TGranja = class
private
FTipoDeAnimales: TList;
FAnimales: TList;
protected
function RegisterClass(AAnimal: TClassAnimal): Integer; virtual;
procedure UnRegisterClass(AAnimal: TClassAnimal); virtual;
public
constructor Create; override;
destructor Destroy; override;
function ComprarAnimal(AAnimal: TAnimal): Integer;
procedure AlimentarTodos;
procedure Alimentar(AAnimal: TAnimal);
procedure IncrementarEurosAlPresupuesto(FEuros: Double);
procedure DisminuirEurosAlPresupuesto(FEuros: Double);
property Name: String read FName write SetName;
property Presupuesto: Double read FPresupuesto;
property AnimalTipoCount: Integer read GetAnimalTipoCount;
property AnimalTipo[Index: Integer]: TClassAnimal read GetAnimalTipo;
property AnimalCount: Integer read GetAnimalCount;
property Animal[Index: Integer]: TAnimal read GetAnimal;
end;

La clase TAnimal, declaraba algunas propiedades y métodos comunes a los descendientes. Os dejo ver la especificación y seguimos reflexionando:

TAnimal = class
private
FNombre: String;
FCoste: Double;
FSexo: TSexo;
FPVP: Double;
FEspecie: String;
procedure SetNombre(const Value: String);
procedure SetCoste(const Value: Double);
procedure SetSexo(const Value: TSexo);
procedure SetPVP(const Value: Double);
procedure SetEspecie(const Value: String);
protected
FGranja: TGranja;
property Coste: Double read FCoste write SetCoste;
procedure ExecuteMethod(AAnimal: TAnimal; const AMetodoName: String); virtual;
public
procedure Execute(const AMetodoName: String); virtual;
procedure Caracterizar; virtual; abstract;
property ConsumoPorComida: Double read FConsumoPorComida write SetConsumoPorComida;
procedure Comer; virtual; abstract;
property Nombre: String read FNombre write SetNombre;
property Especie: String read FEspecie write SetEspecie;
property Sexo: TSexo read FSexo write SetSexo;
property PVP: Double read FPVP write SetPVP;
end;

Respecto a los descendientes de la clase TAnimal, para las pruebas que quise compartir con vosotros, me bastaban cuatro métodos rápidos que al menos le permitan al animal emitir un sonido virtual y poco mas (comer también es saludable ;-)).

Veamos por ejemplo como queda el interfaz y la implementacion de la clase TCaballo:

unit uCaballos;

interface

uses uGranja;

type
TCaballo = class(TAnimal)
public
procedure Execute(const AMetodoName: String); override;
procedure Caracterizar; override;
procedure Comer; override;
procedure Relinchar;
published
procedure Trotar; //< – Interesante (ver comentarios)
end;

implementation

uses Dialogs;

{ TCaballo }

procedure TCaballo.Execute(const AMetodoName: String);
begin
ExecuteMethod(Self, AMetodoName);
end;

procedure TCaballo.Caracterizar;
begin
Relinchar;
end;

procedure TCaballo.Comer;
begin
FGranja.DisminuirEurosAlPresupuesto(ConsumoporComida);
Coste:= Coste + ConsumoporComida;
end;

procedure TCaballo.Relinchar;
begin
ShowMessage(‘IIIIAAAAAAAAAAA…’);
end;

procedure TCaballo.Trotar;
begin
ShowMessage(‘Caballo trotando…’);
end;

end.

Desgargar código fuente del ejemplo

Para situarnos en condicion de reflexionar, preparé rápidamente un pequeño ejemplo lo mas sencillo posible, bastante tonto todo hay que decirlo, con la idea de que se vieran explicitamente los comentarios. Si ya descargasteis el zip con las unidades, en el interior existe un exe para que se pueda ejecutar sin tener que compilar los módulos (el fichero comprimido ha sido revisado con ZoneAlarm Antivirus antes de ser subido al servidor pero no obstante siempre debeis tomar precauciones y revisar cualquier descarga). El codigo se ha compilado con Delphi 2007.

Ahora vienen los comentarios y con vuestro permiso, vamos a seguir ya en tiempo presente. Lo más fácil es que contrasteis lo que se comenta, con el codigo fuente, para así evitar tener que incluirlo tamben en la entrada.

¿Que representa TGranja…?

La clase TGranja es similar a otras clases que nos planteamos en el Framework pero un poco mas sencilla. La idea que perseguimos es crear una clase especializada en cualquier descenciente de una clase base cualquiera, en este caso concreto TAnimal, que es una abstracción que aglutina a todos los animales que podemos encontrar dentro del dominio de TGranja. Así pues, TGranja conoce cuantos animales contiene la factoria o cuantas especies distintas existen. Asimismo, tiene metodos que permiten representar un acto común a todos ellos como lo puede ser el de alimentarse. Hasta aquí todo perfecto, ¿no?

Veamosssss… 🙂

Hay un problema y es el mismo que liga al interfaz principal del Framework con los Módulos de la gestión. Para ser funcional el interfaz, necesitamos desligarlo de los módulos, puesto que si este los “conoce” se ligará a los mismos en un vinculo de dependencia. Lo ideal es que la ventana principal no conozca la clase final a la que accede el usuario. Un detalle como éste, suele identificar la rigidez de algunos desarrollos y el grado de rigidez es directamente proporcional a las relaciones de dependencia entre los modulos.

En el caso que nos ocupa, el de la granja, a nosotros no nos interesa que la misma conozca el animal que acaba de acoger. Esperamos que sea el propio animal el que nos lo diga, y para eso existe o nos valemos del polimorfismo, esa relación misteriosa que nos permite invocar un método en la clase TAnimal (el ascendiente) y que por arte de birli-birloque, mágicamente, responda en el animal correcto.

Ejecutad el ejemplo que se incluye y nos situamos inicialmente en el paso 1. Esta es la figura del mismo:

Paso 1

Podemos añadir varios animales a la granja. Basta seleccionar el animal, marcar la cantidad (por defecto 1) y pulsar sobre el boton cuyo caption es “Vender a la granja”. Repetimos esta accion para que existan al menos un par de animales y podemos pasar al paso numero 2.

Paso 2

En esta ventana se aprecian dos botones en la parte inferior con el rotulo de Alimentar, bien a un animal o bien a todos. Es el ejemplo mas sencillo que comentabamos. En la implemenación de ambos, existen un metodo comun a todos los animales, independientemente de que tipo o especie sean. Comer en la clase TAnimal es una abstracción de una acción que se concreta de forma distinta en cada especie. Asi pues, al ejecutar el método Alimentar de la clase TGranja, la invocación sobre la clase TAnimal, se responde con la respuesta del individuo especifico, que recibe el método gracias a las directivas virtual/abstract en la clase base y override en la clase descendiente. Gracias a las directivas, el metodo que por defecto es Estático y cuyo tipo es declarado en la compilación se convierte en dinamico y la implementación que se invoca responde al tipo real del objeto, en tiempo de ejecución.

También en el paso dos nos encontramos un pequeño problema, que puede ser resuelto facilmente . Queremos que nuestros animales emitan un pequeño sonido virtual, representado simplemente por un mensaje de texto mediante la ShowMessage(). Sin embargo, cada método tiene una firma distinta. El caballo relincha, la vaca muge, el cerdo gruñe… y yo suspiro por coger pronto las vacaciones… 🙂

Así que lo resolvemos interponiendo un método generico que los aglutine. Yo lo he llamado Caracterizar y permite que cada animal concrete su “caracterización”. Al pulsar el animal, representado en un boton del contenedor responderá a nuestra pulsación emitiendo el sonido.

Y nos queda el paso 3 y que responde al codigo fuente que leimos de Neftalí.

Paso 3

¿Como puedo ejecutar un metodo que no existe en todos los animales? Si os fijais en la clase TCaballo, hemos declarado en la interfaz el método Trotar que es característico de esta especie y que no existe en el resto de animales de la granja.

En los dos casos anteriores, hemos sabido encontrar en la clase base un método comun a todos. ¿Que hacemos ahora…?

La mayoria de los programadores que conozco pienso que responderían a esta pregunta echando manos de los interfaces. Una interfaz es un contrato entre la clase y una especificación. El contrato obliga a que la clase implemente una respuesta a los requerimientos que se le van a demandar. Si ligamos la clase TCaballo a la interfaz ITrotar las instacias de la clase responderan correctamente a los métodos que pueda proponer la interfaz. Y tendremos en nuestras manos, las herramientas adecuadas para poder interrogar a la clase y preguntarle si implementa dicha interfaz. El uso de interfaces puede ayudarnos cuando las clases no tienen un ascendente comun.

Podemos dejar el tema de las interfaces para otra entrada del blog, ya que requiere algunos detalles adicionales que se escapan del proposito de esta entrada.

Pero sí podemos valernos del código de Germán Estevez, que se puede ver implementado en la ventana del Paso 3. Si tenemos seleccionado el ejemplar Caballo en el contenedor de animales, y ejecutamos el método Trotar la instancia de TCaballo responderá correctamente a la demanda del método. Si por el contrario, seleccionamos otro animal, la aplicación nos mostrará un aviso de error, comunicandonos que el actual animal no puede ejecutar el procedimiento. Seguimos manteniendo la premisa original que es intentar desligar a la clase especializada (TGranja), de conocer la clase del modulo que maneja (p.e. TCaballo). Inevitablemente, sí tiene que conocer el nombre del método.

Y ya para finalizar un pequeño comentario a esta última posibilidad: un detalle a tener en cuenta es que se condiciona la ejecución, a que el método sea declarado como publicado (published), para que pueda hacer uso de la RTTI o informacion de tipos en tiempo de ejecución. De no haberlo hecho así, no hubieramos podido encontrar la dirección del método Trotar para poder invocarlo.

Por hoy creo que nada mas. Mi deseo de que seais muy felices.

2 comentarios sobre “La Granja

  1. El ejemplo de LA GRANJA es una interesante aplicacion del polimorfismo
    Respecto al codigo Fuente del articulo
    UN ENFOQUE MODULAR PARA NUESTRA APLICACION
    Podria subirlo al BLOG para poder descargarlo
    Gracias de Antemano

  2. Hola Alvaro:

    Voy a escribir una nueva entrada para adjuntar las fuentes, al menos hasta que Jose Luis las pueda añadir junto al artículo.

    Tenía pendiente comentarlo con el pero con la acumulación de tareas pendientes lo olvidé y ya me las pidieron algunos compañeros mas.

    Un saludo,

    Salvador

Los comentarios están cerrados.