PDA

Ver la Versión Completa : Como instanciar objetos de los cuales solo se conoce su clase ancestro


AzidRain
07-07-2008, 00:26:24
Estoy jugando al modelo MVC

Tengo un objeto TController que gestion acciones y algunas reglas de negocio, a este TController actualmente le paso varios descendientes de TForm para que los utilice cuando le haga falta de manera que mi pseudo código queda así:

Instanciar el controlador
Instanciar las vistas que va a usar el controlador
Pasarle las vistas creadas al controlador.


El controlador en este caso solo sabe que las vistas con TForms o sus descendientes, como es obvio estos TForms siempre son descendientes de TForm. Como lo tengo tengo que instanciar primero las TForms y luego pasarlas al Controlador, lo que quisiera es que el controlador fuera capaz de instanciar correctamente cada TForm según lo necesite.

Como el controlador no sabe que clase realmente representa cada TForm no es capaz de llamar al constructor correcto.

Lo más simple sería modificar rápidamente al controlador para que llame al constructor correcto según el caso, pero lo que quiero es que el controlador sea totalmente independiente de la forma que le pase uno y que llame al constructor que corresponda.

Se aceptan sugerencias.

Delphius
07-07-2008, 02:52:01
Hola Azid, después te pasas por la taberna;)

Bueno, con respecto al Modelo MVC no lo he llevado a la práctica exactamente y por lo que comprendo (de lo poco que estuve leyendo sobre este modelo) se basa en la idea del Observador (http://es.wikipedia.org/wiki/Observer_%28patr%C3%B3n_de_dise%C3%B1o%29).

Por lo que comprendo... una vista notifica (envia un mensaje) al controlador y éste aplica los cambios al modelo. Y si es necesario notificar de algo a la vista, el controlador le hace notificar de ello.

En principio, pareciera ser que el controlador no sabe con que vistas interactuará. Y si es así, lo único que conoce es una "interfaz" común a ellas.
Por lo general, en principio, una implementación de este tipo de patrón lo que hace es que el Controlador mantenga una referencia de los objetos vistas... Yo lo veo así: las vistas se suscriben al controlador. Y éste cuando sea necesario, recorre todos los objetos registrados lanzando el evento adecuado pasando como parámetro el "valor" que sea necesario.

La implementación del evento queda a cargo de cada vista.

Si es asi como debería actuar, me parece entonces que el Controlador no es el experto en información adecuado para crear las vistas. Más bien, el no tiene pleno conocimiento de quienes son ni que hacen. Simplemente el les hace saber que "algo cambió"

Por tu descripción del tema, pareciera ser que lo que deseas es implementar una fábrica de vistas.

Y si uno ideas, pareciera ser que el tema puede venir así:
1. Se cre el controlador
2. Se crea la Fábrica
3. la Fábrica crea la vista
4. La vista creada se registra en la "colección" del Controlador para que este le notifique de los cambios.

Ahora el tema de como hacer la referencia desde la vista al controlador, pareciera ser que sería apropiado que la vista tenga visibilidad de atributo sobre el Conntrolador...

TVista = class(TForm)
private
Controler : TControler;
...

De modo que cuando desee enviarle un mensaje, la vista pueda recuperar dicha referencia.

Tal vez, sea la fábrica quien le indique a la vista a que controlador referenciar. Por lo poco que se, me resulta que es una buena candidata. Me parece adecuado, al menos por ahora, que al momento de crear la vista, la fábrica le haga saber el controlador. Por lo pronto, yo me hago la idea de esto:

constructor TVista.Create(Controler: TControler);
begin
inherited Create;
FControler := Controler;
...
end;

Si el tema de la fábrica es lo que se debería seguirse, me queda ahora la duda ¿Quién es el dueño realmente de las vistas? Una respuesta rápida me indicaría que es TAplication. Después de todo... ¿No es él quien tiene y guarda la referencias a todas la formas que componen a la aplicación?

Pero no se que tan viable, y aplicable, sea esta idea. Reconozco que el patrón Observador y el MVC son todavía de estudio para mi.

No se si te he ayudado o confundido más:o. Disculpame amigo, pero más no sabría decirte. Tengo que estudiarlo mejor al patrón.

Saludos,

Al González
07-07-2008, 03:50:37
¡Hola César!

Primero que nada, acá te esperamos, la fiesta durará hasta el martes ;): http://www.clubdelphi.com/foros/showthread.php?t=58049

No sé mucho de modelos (casi todo lo programo según los astros ;)). Pero me salta la siguiente pregunta al leer tu planteamiento:

¿No te sirve pasarle al controlador la clase de objeto que deseas instanciar? Algo así como:

Controlador.SetView (TfmCliente);


Dentro del método SetView, el objeto controlador podría ejecutar la instanciación sin problemas:

Procedure TControlador.SetView (Const Clase :TFormClass);
Var
F :TForm;
Begin
{ En lugar de Nil, puede especificarse otro valor como "Owner"
(por ejemplo Self si TControlador es descendiente de
TComponent) }
F := Clase.Create (Nil);

{ Hacer x cosa con la instancia F }
End;


Recuerda que también los métodos constructores pueden ser virtuales, y ese es el caso del constructor Create que TForm hereda de la clase TComponent. Por lo tanto se estará llamando al constructor que corresponda en caso de que lo hayas redefinido (Override) en algunas de tus clases de formularios.

Espero te resulte oportuna mi aportación. No dejes de comentarnos sobre tus avances.

Saludos.

Al González. :)

Neftali [Germán.Estévez]
07-07-2008, 09:23:45
Como el controlador no sabe que clase realmente representa cada TForm no es capaz de llamar al constructor correcto.

Lo más simple sería modificar rápidamente al controlador para que llame al constructor correcto según el caso, pero lo que quiero es que el controlador sea totalmente independiente de la forma que le pase uno y que llame al constructor que corresponda.

Bueno, independientemente de tu modelo, sólo se me ocurren dos formas de hacer eso:
(1) A partir del nombre y intentando obtener la referencia a la clase utilizando RTTI; Ya lo hemos discutido muchas veces por aquí.
(2) Teniendo en algun sitio un "diccionario" (que no deja de ser algo simnilar a lo que hace RTTI) que sirve de "apoyo" a tu controlador donde tengas todas las instancias y sus constructores.


// Definición de la instancia
TInstancia1 = class(TForm)
....



Type
// Tipo apuntador al constructor
TFormInstancia1Class = class of TInstancia1;


En tu diccionario debes guardar los apuntadores; En mi caso suelo guardar una pareja, ID+ApuntadorConstructor. Con ese diccionario el controlador puede crear las instancias del tipo correcto.

Delphius
10-07-2008, 17:29:54
Hola Azid,
¿Lograste avanzar en algo? ¿Tienes novedades?

Saludos,

AzidRain
11-07-2008, 01:29:29
Finalmente conseguí que funcionara el modelado como lo había concebido, aunque creo que todavía puede mejorarse y obviamente no es nada del otro mundo.

Es un modelo simple para manejar una tabla o query que represente un catálogo, es decir, esas formas que muestran en un grid todos los registros de la tabla. Este tipo de ventanas son muy comunes en programas administrativos y en uno que estoy haciendo requiero hacer lo mismo muchísimas veces, cambiando solamente el catálogo en cuestión.

Asi que para armar un catálogo completo necesitamos:

1.- Controlador de Grid (o grilla)
2.- Una vista (forma) principal (la que tiene el grid)
3.- Una vista (forma) para editar un registro (que es la misma para cambiarlo)
4.- Un modelo (tabla) que representa los datos a visualizar

En clases es así:
1.- TdxGridControlador
2.- TdxGridForm
3.- TdxDialogForm
4.- TZquery + TDataSource [+ TUpdateSQL si usamos un query multitablas]

TdxGridView se vé así:
http://img133.imageshack.us/img133/4645/dxgridht4.jpg

y TdxDialogForm se vé así:
http://img65.imageshack.us/img65/1998/dxdialogxb5.jpg


Pues bien la cosa es así:

Tenemos una clase TdxControlador que es la clase base para construir diferentes tipos de controladores simples. De ahi derivé TdxGridControlador que es un controlador especializado en manejar un grid y hacer las famosas Altas-Bajas-Modificaciones, esta sería su declaración:



TAuxForma = Class of TTdxDialogForm;

TdxGridControlador = class(TdxControlador)
Public
TablaDatos:TDataset;

constructor Create( ClaseForma : TClassForm);override;
procedure OnNuevoRegistro(sender:TObject);virtual;abstract;
procedure OnCambiarRegistro(sender:TObject);virtual; abstract;
procedure OnBorrarRegistro(sender:TObject);virtual;

procedure OnBotonOk(sender:TObject); override;
procedure OnBotonCancel(sender:TObject);override;



protected
Procedure EnNuevo(aForm:TAuxForma);virtual;
Procedure EnEdita(aForm:TAuxForma);virtual;

end;


Como podemos ver se trata prácticamente de enventos, que serán disparados de acuerdo con el diseño de que se trate. Tanto OnNuevoRegistro como OnCambiaRegistro son métodos abstractos por lo que hay que definir que queremos que pase en cada caso. Por su parte OnBotonOk y OnBotonCancel son métodos que se ejecutan cuando aparecen los diálogos para un nuevo registro (TdxDialogForm) o para cambiarlo.

Para utilizar todo este andamiaje, veamos un ejemplo para una tabla Empleados, hacemos lo siguiente:

1.- Crear el query en un datamodule y prepararlo como se requiera, aquí podemos incluir condiciones para nuevos campos, chequeo de índices, etc. lo que normalmente se hace.

2.- Crear la vista principal derivando una nueva forma de TdxGridForm y ajustamos los valores del Grid (en mi caso ocupo mis amadas DevExpress pero con un grid normal funciona igual) para utilizar el query que creamos anteriormente (databinding). Es importante ver que este query contiene un TActionList que ya está preparado para trabajar con el controlador por lo que no hay que tocar nada de eventos. Se llamara TFCatEmpleados:
http://img137.imageshack.us/img137/892/catempleadosnm5.jpg

3.- Creamos la vista o vistas para Agregar o editar un registro, colocamos los controles que necesitemos y hacemos nuevamente databinding con la misma query.Se llamará TFNuevoEmpleado.:
http://img509.imageshack.us/img509/8317/nuevoempleadons4.jpg


4.- Finalmente preparamos el controlador derivando una nueva clase a partir de TdxGridControlador, únicamente tenemos que indicarle de que clase son las formas que utilizaremos para la edición de registros.



Type
TCEmpleados = class(TdxGridControlador)

Procedure CierraForma(sender:TObject);

{*** Eventos para la vista principal ***}

procedure OnNuevoRegistro(sender:TObject);override;
procedure OnCambiarRegistro(sender:TObject);override;


end;

implementation

{ TCEmpleados }

procedure TCEmpleados.CierraForma(sender: TObject);
begin

end;

procedure TCEmpleados.OnCambiarRegistro(sender: TObject);

begin
EnEdita(TFNuevoEmpleado);
end;

procedure TCEmpleados.OnNuevoRegistro(sender: TObject);
Begin
EnNuevo(TFNuevoEmpleado);
end;

end.

implementation

end.


5.- En la parte del programa donde vamos a utilizar este andamiaje preparamos todo y lo ejecutamos:



procedure TMainForm.AEmpleadosExecute(Sender: TObject);
var cE: TCEmpleados;

begin
{Crear el controlador y pasarle la vista principal}

cE := TCEmpleados.create( TFCatEmpleados );
ce.TablaDatos.Open;
Datos.ZPuestos.Open; //Tabla auxiliar para los puestos
Try
ce.MuestraVistaPrincipal;
Finally
Datos.ZPuestos.Close;
ce.TablaDatos.Close;
ce.Free;

end;

end;


Y obtenemos esto:
http://img75.imageshack.us/img75/1217/vistafinal1zt0.jpg
Al hacer click por ejemplo en Editar, obtendremos:
http://img244.imageshack.us/img244/3664/vistafinal2ta0.jpg

Igualmente con nuevo. El botón de borrar por defecto lo único que hace es preguntar si se está seguro y borrar el registro actual.

Asi de simple, no hay que hacer nada adicional, claro si se de desea se pueden extender más las clases y agregar más cosas, pero así como está me sirve y me mantiene un orden más o menos adecuado en lo que voy haciendo.

Y claro...no es el hilo negro eh...que conste.

AzidRain
11-07-2008, 01:30:46
A ver si algun moderador me compone el post porque otra vez no funcionan las etiquetas para poner imágenes...me lleva.. me tarde 30 minutos para escribir todo, sacar las pantallas, subirlas, pegarlas para que al final esta mugre de VBulletin no sirva...

Delphius
11-07-2008, 02:58:17
Me alegro mucho de que hayas logrado resolver tu problema.
Gracias por compartir con nosotros la solución que planteaste, tendré en cuenta tu aporte para continuar con mi investigación y estudio sobre esta área (la cual admito que es una zona gris en mi mar del conocimiento).

No te hagas drama por las imágenes. Está disponible el enlace. Lo importante es que lograste avanzar.

Saludos amigo,