Como reza esa primera imagen que abre el post, es un buen momento para intentar razonar en terminos de clases, y experimentar con el ejemplo que intentabamos abordar y que nos servía para reflexionar. En él, podríamos capturar la idea de atributo, como ya se ha podido extraer de las dos entradas anteriores, consistía en un rasgo diferenciador que combinado con otros, nos servía para identificar un artículo determinado. Podemos convenir aceptar la clase TAtributo como una representación de ese rasgo.
Y siguiendo con ese razonamiento, también parece que se desprende de la misma existencia de ese atributo, la idea de que va a depender de otra entidad que sea capaz de gestionarlos, ya que será necesaria la existencia de uno o mas atributos y por lo tanto, necesitamos «algo» que sea capaz de operar con ellos y relacionarlos. A esta clase, le podríamos llamar TGenerador, por buscar un nombre que resulte familiar a esa idea de «artefacto» que va a mostrar las distintas combinaciones de codigo posibles para formar nuestro artículo.
¿Qué os parece si empezamos a trabajar?
Vamos a resumir lo que tenemos hasta ahora. Partimos de esas dos ideas: TGenerador y TAtributo. Podemos empezar a perfilarlas, con dos operaciones básicas, como pueden ser:
* Añadir un atributo y
* Cambiar la posición de dos de ellos.
Vamos a escribir unas lineas de código y un pequeño ejemplo que las ponga en práctica, para cercionarnos de que los razonamientos son correctos. Para poder diferenciar los atributos en nuestro interfaz de prueba, hemos implementado la propiedad captión que nos permite visualizarlos de forma sencilla dentro de un listbox.
unit UPensarEnClases; interface uses SysUtils, Classes; type TAtributo = class; TGenerador = class(TObject) private FAtributos: TList; function GetAtributos(Indice: Integer): TAtributo; protected public constructor Create; destructor Destroy; override; function AddAtributo: TAtributo; function Count: Integer; procedure SetUpPosition(AAtributo: TAtributo); procedure SetDownPosition(AAtributo: TAtributo); function GetPosicion(AAtributo: TAtributo): Integer; property Atributos[Indice: Integer]: TAtributo read GetAtributos; end; TAtributo = class(TObject) private FGenerador: TGenerador; FCaption: String; procedure SetCaption(const Value: String); protected public function GetPosicion: Integer; constructor Create(AGenerador: TGenerador); virtual; destructor Destroy; override; property Caption: String read FCaption write SetCaption; end; implementation { TAtributo } constructor TAtributo.Create(AGenerador: TGenerador); begin if not Assigned(AGenerador) then Raise Exception.Create('Error: Referencia al generador no valida'); FGenerador:= AGenerador; end; destructor TAtributo.Destroy; begin FGenerador:= Nil; inherited; end; function TAtributo.GetPosicion: Integer; begin Result:= FGenerador.GetPosicion(Self); end; procedure TAtributo.SetCaption(const Value: String); begin FCaption := Value; end; { TGenerador } function TGenerador.AddAtributo: TAtributo; begin Result:= TAtributo.Create(Self); FAtributos.Add(Result); end; function TGenerador.Count: Integer; begin Result:= FAtributos.Count; end; constructor TGenerador.Create; begin FAtributos:= TList.Create; end; destructor TGenerador.Destroy; var i: Integer; f: TAtributo; begin for i:= 0 to Count - 1 do begin f:= TAtributo(FAtributos[i]); FreeAndNil(f); end; FAtributos.Clear; FreeAndNil(FAtributos); inherited; end; function TGenerador.GetAtributos(Indice: Integer): TAtributo; begin if (Indice < 0) or (Indice > FAtributos.Count-1)then Raise Exception.Create('Error indice get fuera de rango'); Result:= FAtributos[Indice]; end; function TGenerador.GetPosicion(AAtributo: TAtributo): Integer; begin Result:= FAtributos.IndexOf(AAtributo); end; procedure TGenerador.SetDownPosition(AAtributo: TAtributo); var i: Integer; begin i:= FAtributos.IndexOf(AAtributo); if (i < Count -1) then FAtributos.Exchange(i, i+1); end; procedure TGenerador.SetUpPosition(AAtributo: TAtributo); var i: Integer; begin i:= FAtributos.IndexOf(AAtributo); if (i > 0) then FAtributos.Exchange(i, i-1); end; end.
Finalmente, vamos a ver el pequeño formulario que nos permite verificar que nuestro interfaz e implementación hacen lo que le hemos solicitado.
Al ser creado el formulario, crearemos el generador y añadiremos los atributos (3) y daremos la oportunidad a nuestro usuario de que los cambie de posición de los mismos.
Es muy sencillo… upsssss… (aquí tenéis)
unit UMain; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, uPensarEnClases; type TfrmPensarEnClases = class(TForm) lbxAtributos: TListBox; btnSubir: TButton; btnBajar: TButton; Label1: TLabel; procedure btnSubirClick(Sender: TObject); procedure btnBajarClick(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } procedure ActualizarInterfaz; public { Public declarations } FGenerador: TGenerador; end; var frmPensarEnClases: TfrmPensarEnClases; implementation {$R *.dfm} procedure TfrmPensarEnClases.ActualizarInterfaz; var i: Integer; begin lbxAtributos.Clear; for i := 0 to FGenerador.Count - 1 do lbxAtributos.Items.Add(FGenerador.Atributos[i].Caption); end; procedure TfrmPensarEnClases.btnBajarClick(Sender: TObject); var fAtrib: TAtributo; i: Integer; begin i:= lbxAtributos.ItemIndex; fAtrib:= FGenerador.Atributos[i]; if i >= 0 then begin with FGenerador do SetDownPosition(fAtrib); ActualizarInterfaz; lbxAtributos.Itemindex:= fAtrib.GetPosicion; end; end; procedure TfrmPensarEnClases.btnSubirClick(Sender: TObject); var fAtrib: TAtributo; i: Integer; begin i:= lbxAtributos.ItemIndex; fAtrib:= FGenerador.Atributos[i]; if i >= 0 then begin with FGenerador do SetUpPosition(fAtrib); ActualizarInterfaz; lbxAtributos.Itemindex:= fAtrib.GetPosicion; end; end; procedure TfrmPensarEnClases.FormCreate(Sender: TObject); begin FGenerador:= TGenerador.Create; with FGenerador do begin AddAtributo.Caption:= 'Caracteristica'; AddAtributo.Caption:= 'Color'; AddAtributo.Caption:= 'Tipo'; end; ActualizarInterfaz; lbxAtributos.ItemIndex:= 0; end; procedure TfrmPensarEnClases.FormDestroy(Sender: TObject); begin if Assigned(FGenerador) then FreeAndNil(FGenerador); end; end.
Algunas ideas nos pueden ayudar en esos primeros razonamientos:
* En este caso concreto, tanto la clase TGenerador como TAtributo, han descendido de TObject, que es el ascendente comun a todas las clases. Podríamos haber razonado que, por ejemplo, TAtributo, descendiera de un control visual, pudiendo ser insertado en nuestro formulario, lo cual no es ni mas ni menos correcto, ya que forma parte del abanico de decisiones de diseño que nuestra experiencia puede valorar. Hacerlo de una forma o de otra, va a condicionar necesariamente las relaciones a las que voy a quedar ligado. Quizás por esa razón y por evitar ligarnos a ninguna representación es por lo que he optado por partir de TObject. Como hemos hecho que descienda de ésta, necesitábamos un procedimiento que sea capaz de recrear la instancia del Generador por lo cual, se ha implementado ActualizarInterfaz. Tambien se ha hecho necesario destruir explicitamente la instacia del Generador y liberar la reserva de memoria que ha necesitado su creación. Lo hemos hecho en el evento OnDestroy de nuestro formulario.
* La clase TGenerador, necesita almacenar una lista de referencias a las distintas instacias de TAtributo, y para ello, una buena opción es hacer uso de la clase TList, que es especificada e implementada dentro del módulo Classes.pas. TList es una lista de punteros y un aliado extremadamente poderoso, ya que puede almacenar cualquier cosa. En contrapartida, la manipulación de cualquiera de las referencias a las que apuntamos desde nuestra lista de punteros, necesitaría inevitablemente de una conversión de tipos para acceder al objeto instanciado.
Este módulo es bastante interesante pues contiene las definiciones de algunas clases muy básicas que en buena medida se correlacionan con algunos TDA´s clásicos de la programación, como la gestión de listas, pilas y colas. Estos dos ultimos (pilas y colas) se apoyan en la clase TList y se especifican e implementan en el modulo Constnrs.pas. En Classes.pas, además se incluyen los hilos de ejecución (TThreads), las interfaces, los enumeradores, la clase TStream y otras más, imprescindibles y recurrentes dentro del esquema de la VCL.
La miembros y metodos de TList, nos permiten de forma sencilla, generar por ejemplo el acceso a un atributo determinado, a través de una propiedad de tipo array que se gestiona con el índice de la lista. Tambien nos permite intercambiar de posición dos referencias. No tenemos que ir reinventando la rueda en aquellos puntos en los que nos podamos valer de clases que ya nos dan una funcionalidad. ¿no os parece?.
* Otra idea interesante, podría ser que el atributo conozca a la clase que va a gestionarlo, lo cual nos permitirá, invocar metodos que se ejecutan desde ésta, de ser necesario. Por ello, la creación del mismo atributo, parte del generador, que entrega una referencia a si mismo en el constructor de la clase TAtributo:
constructor Create(AGenerador: TGenerador); virtual;
La invocacion de AddAtributo instancia un nuevo atributo y lo añade a la lista, devolviendo como valor de retorno una referencia al objeto creado.
function TGenerador.AddAtributo: TAtributo;
begin
Result:= TAtributo.Create(Self);
FAtributos.Add(Result);
end;
El constructor guarda esta referencia:
constructor TAtributo.Create(AGenerador: TGenerador);
begin
if not Assigned(AGenerador) then
Raise Exception.Create(‘Error: Referencia al generador no valida’);
FGenerador:= AGenerador;
end;
Podemos ver un ejemplo en el código que hemos escrito. El método GetPosición de la clase TAtributo, nos devuelve la posición del mismo dentro de la lista. Esto podría obtenerse de varias formas. Guardando en un miembro privado el valor de esta posición cuando es añadido el atributo, siendo actualizado cada vez que es modificada la posición del mismo en la lista. Esa podría haber sido una opción. Sin embargo, finalmente, al guardar la referencia al generador, obtener ese indice es tan fácil como preguntarle a quien realmente lo sabe: 🙂
function TAtributo.GetPosicion: Integer;
begin
Result:= FGenerador.GetPosicion(Self);
end;
Lo dejamos aquí y nuestro siguiente paso será ir añadiendo detalles a este escenario.
Deja una respuesta