Вы находитесь на странице: 1из 29

Creado el 16/06/09

4 Accediendo a un
Objeto y a sus Datos

Conceptos principales

El nombre del objeto como referencia Creando y liberando un objeto: Constructores y Destructores La necesidad de la gestin de memoria: fugas de memoria y colgando referencias Mtodos de acceso a datos: Get y Set Propiedades de acceso a datos: Mapeados y directos, especificaciones de lectura y escritura El concepto de Patrones en la orientacin a ojetos El patrn inmutable Creando una lista de referencias a objetos

Aprendiendo OOP con Delphi

Pgina 1 de 29

Creado el 16/06/09

Introduccin
Este captulo contina el tema del captulo 3, el cual introduca una clase y un objeto definidos por el programador y contrastaba esto con las definiciones RAD anteriores de ste mdulo. En ste captulo vimos la creacin y liberacin de un objeto y exploramos el concepto de un constructor y un destructor. Vimos los mtodos de acceso Get y Set y sus definiciones de propiedades. (Las propiedades proporcionan una alternativa a los mtodos de acceso) Tanto en Delphi como en Java, un nombre de objeto no es el objeto en s mismo: es una referencia al objeto al cual el programa automticamente dereferencia. Consideraremos algunas consecuencias de sta forma de direccionar un objeto. Cuando escribimos programas, problemas y soluciones similares se pueden producir de forma repetitiva. Esto nos lleva a la cuestin de cmo tomar ventaja de la experiencia pasada cuando escribimos nuevos programas. Una forma de hacer esto es crear libreras de cdigo para operaciones comunes y para usar esto en cualquier lugar apropiado. La programacin orientada a objetos proporciona buen soporte para ste tipo de reutilizacin de cdigo a travs de la herencia y la composicin. Tambin hay otras situaciones donde no podemos reutilizar el cdigo actual, pero donde hay conceptos que hemos utilizado con anterioridad y queremos reutilizar dichos conceptos. Tener la capacidad de reutilizar conceptos ya creados y probados es til en otros muchos campos adems de en la programacin. En arquitectura, por ejemplo, existen problemas similares que surgen una y otra vez en el diseo de edificios. A finales de los 70 un grupo de arquitectos liderados por Christopher Alexander introdujo el concepto de Patrones. A mediados de los 90 ste concepto se adapt a los problemas de la programacin. Los patrones fueron diseados para sugerir soluciones efectivas a problemas recurrentes y para encapsular experiencia. En ste mdulo trabajamos a travs de un nmero de patrones como forma de estudiar cmo los expertos usan y aplican tcnicas de programacin OO. Este captulo presenta nuestro primer patrn, Immutable, el cual nos permite crear un objeto cuyos valores de datos no pueden ser cambiados, una vez el objeto ha sido creado.

Un Programa Ejemplo
Usamos varias variaciones de un mismo programa (Figura 1) para ilustrar los principios que cubre ste captulo. Crearemos una clase, TClient, con campos de datos CName para el nombre del cliente y AccNo para un nmero de cuenta. Crearemos el objeto, estableceremos y leeremos sus datos y los liberaremos en los ejemplos siguientes.

Figura 2: Componentes en la Figura 1: Ilustrando Getters y Setters interfaz de usuario

Aprendiendo OOP con Delphi

Pgina 2 de 29

Creado el 16/06/09

Ejemplo 4.1: Mtodos de Acceso


Para proporcionar encapsulacin, las clases normalmente tienen datos privados con mtodos de acceso pblicos, tal como ilustra la clase TItem en el captulo anterior. Los mtodos de acceso son frecuentemente denominados Getters y Setters, o mtodos Get y Set, o Accesores y Mutadores o, colectivamente, mtodos de acceso, y proporcionan los medios para el resto del programa para almacenar, manipular y leer los valores de los datos privados del objeto.

Ejemplo 4.1 paso 1: Implementando Mdotos de Acceso


Inicie un nueva aplicacin. Aada una segunda unit a travs de la secuencia de men File | New | Unit. Almacene sta unit adicional como ClientU.pas e introduzca el siguiente cdigo:
1 unit ClientU; 2 interface 3 type 4 TClient = class (TObject) 5 private 6 FAccNo: string; 7 FCName: string; 8 public 9 function GetAccNo: string; // Get access methods 10 function GetCName: string; 11 procedure SetAccNo(const AnAccNo: string); // Set access method 12 procedure SetCName(const ACName: string); 13 end; // end TClient = class (TObject) 14 implementation 15 16 17 18 19 { TClient } function TClient.GetAccNo: string; begin Result := FAccNo; end; // end function Tclient.GetAccNo

20 function TClient.GetCName: string; 21 begin 22 Result := FCName; 23 end; // end function Tclient.GetCName 24 procedure TClient.SetAccNo (const AnAccNo: string); 25 begin 26 FAccNo := AnAccNo; 27 end; // end procedure Tclient.SetAccNo 28 procedure TClient.SetCName (const ACName: string); 29 begin 30 FCName := ACName; 31 end; // end procedure Tclient.SetCName 32 end. // end ClientU

Como es normal en OO, mantenemos el dato privado (lneas 57). Para proporcionar acceso a los datos, los mtodos de acceso son definidos como pblicos (lneas 812). Los Obtenedores (Getters) (lneas 910) son usualmente funciones, ya que retornan un valor. Los Establecedores (Setters) (lneas 1112) son normalmente procedimientos, ya que no necesitan retornar ningn valor, y su valor entrante es proporionado por un parmetro.

Aprendiendo OOP con Delphi

Pgina 3 de 29

Creado el 16/06/09

Esta unit ilustra un nmero de convenciones Delphi. Un campo de datos de clase es precedido por una F, as los campos de datos son llamados FCName y FAccNo (lneas 67). Los parmetros estn frecuentemente precedidos por A o An, y aqu tenemos ACName y AnAccNo (lneas 12, 11). Un mtodo Get es precedido por Get (lneas 910) y un mtodo Set por Set (lneas 1112). Estos prefijos modifican los nombres base CName y AccNo para proporcionar una notacin consistente que simplifique la lectura del cdigo.

Ejemplo 4.1 paso 2: Usando el Objeto


Ahora que tenemos la definicin de clase (paso 1) podemos usarla en la clase de interfaz de usuario. Configure la interfaz de usuario como en las figuras 1 y 2 y luego edite Unit1 como se muestra debajo. Primero declararemos un nombre a usar como referencia para acceder al objeto como un campo de datos privado de la clase de interfaz de usuario (lnea 19 debajo). Esta unit de definicin de clase debe por tanto aparecer en la clusula global uses (ClientU en lnea 5). Creamos el objeto (lnea 38) y luego lo inicializa a travs de sus mtodos Set (lneas 3940). En ste ejemplo, usamos el objeto de forma muy simple, accediendo a l a travs de mtodos Get (lneas 5253), y luego liberndolo cuando hemos terminado con l (lneas 5758).
1 unit ObjAccessU; 2 interface 3 uses 4 Windows, Messages, SysUtils, Variants, Classes, Graphics, 5 Controls, Forms, Dialogs, StdCtrls, ClientU; 6 type 7 TfrmAccessObject = class(TForm) 8 15 16 17 18 19 20 { Standard RAD component declarations } procedure procedure procedure private NewClient: end; btnCreateClick(Sender: TObject); btnFreeClick(Sender: TObject); btnShowClick(Sender: TObject); TClient; // Private reference; initialised to nil

21 var 22 frmAccessObject: TfrmAccessObject; 23 implementation 24 {$R *.dfm} 25 {TfrmAccessObject} 26 procedure TfrmAccessObject.btnCreateClick(Sender: TObject); 27 begin 28 if NewClient <> nil then // avoid memory leakage 29 begin 30 ShowMessage ('Free existing object first'); 31 Exit; 32 end; 33 34 35 36 37 if (edtCName.Text = '') or (edtAccNo.Text = '') then begin ShowMessage ('Please enter a name and number'); Exit; end;

Aprendiendo OOP con Delphi

Pgina 4 de 29

Creado el 16/06/09

38 NewClient := TClient.Create; 39 NewClient.SetCName (edtCName.Text); 40 NewClient.SetAccNo (edtAccNo.Text); 41 edtCName.Clear; 42 edtAccNo.Clear; 43 edtCName.SetFocus; 44 end; // end procedure TfrmAccessObject.btnAddClick 45 procedure TfrmAccessObject.btnShowClick(Sender: TObject); 46 begin 47 if NewClient = nil then // Dont refer to a non-existent object 48 begin 49 ShowMessage ('Create object first'); 50 Exit; 51 end; 52 lblName.Caption := 'Name: ' + NewClient.GetCName; 53 lblAccNo.Caption := 'Acc No: ' + NewClient.GetAccNo; 54 end; // end procedure TfrmAccessObject.btnShowClick 55 procedure TfrmAccessObject.btnFreeClick(Sender: TObject); 56 begin 57 NewClient.Free; 58 NewClient := nil; // NB reset the reference! 59 lblName.Caption := 'Name: '; 60 lblAccNo.Caption := 'Acc No: '; 61 edtCName.SetFocus; 62 end; // end procedure TfrmAccessObject.btnFreeClick 63 end. // end unit ObjectDriverU

Ejecute y testee ste programa. Introduzca valores para el nombre y el nmero de cuenta, cree el ojeto y displyelo. Compruebe que los datos se muestran correctamente. Libere el objeto, introduzca nuevos valores, cree un nuevo objeto y displaye sus valores. Una vez haya comprobado que funciona, introduzca algunos errores. Pique una segunda vez sobre el botn Create sin antes liberar el objeto anterior. Libere un objeto y luego intente displayarlo antes de crear un nuevo objeto. Intente liberar un objeto dos veces. Ahora que est familiarizado con cmo funciona el programa, analicemos el cdigo. Estamos familiarizados con el uso del contructor Create desde el captulo 3, y hemos usado los mtodos Get antes (GetCount en captulo 3). Esta es la primera vez que usamos mtodos Set (lneas 3940, paso 1, lneas 24 31), y funcionan como procedimientos normales. Los mtodos pblicos Set y Get controlan el acceso desde el mundo exterior a los campos de datos privados. Advierta el cuidado que ponemos en comprobar la existencia del objeto en varios puntos del programa. Antes de crear el objeto hemos comprobado si el objeto existe ya, comprobando si la referencia a su nombre apunta o no a nil (lnea 28)1. Si la referencia NewClient es nil, podemos crear un objeto en su momento (p.ej., lnea 38). Si la referencia NewClient no es nil en la lnea 28 el objeto ya existe. Si simplemente creamos otro objeto, El objeto anterior permanecer en memoria pero el nombre NewClient nunca ms ser referenciado. As que el objeto anterior es ahora inaccesible para el programa, ocupando espacio en memoria pero no pudiendo ser usado por el programa. Esto a veces es referido como fuga de memoria (para prevenir fugas de memoria tpicamente slo crearemos un objeto y lo asignaremos a un nombre cuando el nombre se refiera a nil). Si el nombre no se refiere a nil, el manejador de evento anterior muestra un aviso de que un objeto ya existe y termina (lneas 3031). Por el contrario, antes de mostrar los valores de datos del objeto (lneas 5253) nos aseguramos de que el objeto actualmente existe (lnea 47), ya que la referencia a un objeto no existente es un error.
1 Cuando creamos un objeto, Delphi automticamente inicializa las referencias de objeto entre 1 y nil. As que ste valor ser nil la primera vez que el manejador de evento se ejecute.

Aprendiendo OOP con Delphi

Pgina 5 de 29

Creado el 16/06/09

Cuando creamos un objeto, Delphi automticamente inicializa referencias al objeto 1 a nil. As que ste valor ser nil la primera vez que se ejecute el manejador de evento. Despus de todo esto teniendo cuidado de comprobar la existencia del objeto en los dos primeros manejadores de eventos, parece que hacemos caso omiso de todas estas precauciones en la liberacin del objeto (lnea 57). Deberamos liberar el objeto slo si existe, y no intentar liberarlo si no existe? En otras palabras, debera el evento OnClick de btnFree ser:
procedure TfrmAccessObject.btnFreeClick(Sender: TObject); begin if NewClient <> nil then begin NewClient.Free; NewClient := nil; end; { .. etc .. } end; // end procedure TfrmAccessObject.btnFreeClick

Mientras esto funcione, esto no es necesario: el mtodo Free ya incorpora el testeo para nil, y slo destruye el objeto si su referencia no es nil. As, el estamento de programa:
ObjectName.Free;

tiene el mismo efecto que:


if ObjectName <> nil then ObjectName.Destroy;

Advierta las diferentes connotaciones que Free y Destroy tienen en Delphi. Debido a que comprueba la existencia de un objeto, la liberacin de un objeto no existente es segura. Sin embargo, la destruccin de un objeto no existente no es segura ya que Destroy no comprueba antes la existencia del objeto. Es por esto por lo que usamos el mtodo Free en lugar de Destroy. Hay tambin otro aspecto a vigilar. Advierta que tras la llamada a Free, establecemos la referencia a nil. Una llamada a Free debera ser seguida inmediatamente por el establecimento de la referencia a nil:
ObjectName.Free; ObjectName := nil;

Si no lo hacemos, si simplemente liberamos el objeto sin ms, el programa puede ms tarde intentar usar sta referencia para un objeto que ya no existe -una situacin peligrosa denominada referencia colgante-. Para ayudar a protegernos contra referencias colgantes, Delphi proporciona el procedimiento FreeAndNil. Podemos reemplazar estas dos lneas con FreeAndNil(NombreObjeto); y tendremos el mismo efecto. Al igual que Free, FreeAndNil es seguro para usar aunque el objeto no exista. Los cuelgues de memoria y las referencias colgantes son fuentes importantes de errores en un programa que pueden ser muy difciles de localizar, as que es importante tener cuidado a la hora de crear y destruir objetos. Volveremos a esto ms tarde en ste captulo.

Ejemplo 4.2: Constructores y Destructores de Objetos


En ste y en anteriores ejemplos hemos usado el mtodo Create pero todava no hemos escrito un mtodo Create en las clases que hemos definido. Similarmente, tenemos objetos liberados pero no hemos escrito ningn destructor. Esto es porque hemos usado el constructor Create y el destructor Destroy (via Free) que estn heredados desde TObject.

Aprendiendo OOP con Delphi

Pgina 6 de 29

Creado el 16/06/09

Normalmente el constructor y destructor heredados son suficientes para nuestras necesidades. Sin embargo, a veces escribimos un constructor y/o destructor: un constructor nos permite inicializar los campos de datos del objeto a valores determinados como parte del proceso de creacin del objeto, mientras que un destructor nos permite hacer determinadas tareas de limpieza.

Ejemplo 4.2 - paso 1: Implementando un Constructor y Destructor


Modifique la definicin de la clase TClient en ClientU.pas aadiendo un constructor (lneas 9, 1924 debajo) y un destructor (lneas 10, 2529).
1 unit ClientU; 2 interface 3 type 4 TClient = class (TObject) 5 private 6 FAccNo: string; 7 FCName: string; 8 public 9 constructor Create (ACName, AnAccNo: string); 10 destructor Destroy; override; 11 function GetAccNo: string; 12 function GetCName: string; 13 procedure SetAccNo(const AnAccNo: string); 14 procedure SetCName(const ACName: string); 15 end; // end TClient = class (TObject) 16 implementation 17 uses Dialogs; // for ShowMessage below 18 { TClient } 19 constructor TClient.Create(ACName, AnAccNo: string); 20 begin 21 inherited Create; // First create thru superclasss constructor 22 FAccNo := AnAccNo; // then initialise data field values 23 FCName := ACName; 24 end; // end constructor Tclient.Create 25 destructor TClient.Destroy; 26 begin 27 ShowMessage ('About to destroy client object'); // 'tidy up' 28 inherited Destroy; // then destroy thru superclass's destructor 29 end; // end destructor Tclient.Destroy 30 { Methods GetAccNo, GetCName, SetAccNo and SetCName do not change }

Un constructor no es como una definicin de procedimiento. Puede tener parmetros (ACName y AnAccNo en lnea 19) y puede, por ejemplo, establecer los campos de datos del objeto (lneas 2223). Primero vimos la palabra inherited en el ejemplo 2.2, paso 1, ahora Delphi la ha insertado automticamente como parte del proceso VFI. Aqu vemos la palabra clave inherited seguida de un nombre de mtodo (lnea 21). Usamos inherited aqu para invocar al mtodo Create definido en la superclase. Es la primera lnea del constructor, as que el constructor comienza usando inherited Create para crear el objeto (lnea 21). Las lneas 2223 luego inicializan los campos de datos del objeto (Delphi inicializa los campos de datos automticamente para cadenas vacas, y usamos el constructor para establecer distintos valores de inicio).

Aprendiendo OOP con Delphi

Pgina 7 de 29

Creado el 16/06/09

Un destructor tambin sigue la estructura estndar de un procedimiento (lneas 2529). En ste ejemplo no tenemos un trabajo ordenado que hacer, as que mostraremos un mensaje para ilustrar el principio. Considerando que la llamada a inherited Create es el primer estamento de programa en un constructor (lnea 21), la llamada a inherited Destroy es normalmente el estamento final en un destructor (lnea 28). Siguiendo los comentarios sobre Destroy y Free del ejemplo 4.1, paso 2, no queremos llamar directamente a Destroy. En su lugar, llamaremos a Free el cual llamar a ese destructor que hemos escrito antes (lneas 2529) si el objeto existe.

Ejemplo 4.2 paso 2: Usando el Constructor y el Destructor


Ahora modificaremos ObjectAccessU para usar ste constructor (lnea 38 debajo: hemos aadido dos parmetros). No necesitaremos ya ms inicializar los campos de datos separadamente (ejemplo 4.1, paso 2, lneas 3940), as que la escritura de nuestro propio constructor simplifica y asegura la inicializacin. No se necesita ningn cambio en el cdigo para acomodar al destructor.
26 procedure TfrmAccessObject.btnCreateClick(Sender: TObject); 27 begin 28 { if tests as before } 38 NewClient := TClient.Create (edtCName.Text, edtAccNo.Text); 39 { NewClient.SetCName (edtCName.Text); // no longer needed 40 NewClient.SetAccNo (edtAccNo.Text); } 41 { ... etc ... }

42 end; // end procedure TfrmAccessObject.btnAddClick

Ejecute y pruebe sta versin de programa. La creacin del objeto funciona como antes, pero cuando liberamos al objeto obtenemos antes un mensaje antes de que el objeto sea destrudo. Este mensaje viene del destructor que hemos aadido (paso 1 lnea 27) y muestra que Free actualmente llama al destructor. Si pulsamos el botn Free una segunda vez (sin antes crear el objeto de nuevo), el mensaje no aparecer. Por qu no? La pulsacin en el botn Free invoca al mtodo Free. Como ya hemos visto, Free comprueba si el objeto existe. Si lo hace, llama al mtodo destructor Destroy y el mensaje es displayado. Si el objeto no existe (p.ej., ya ha sido liberado), Free no llama al destructor y por ello el mensaje no aparece en pantalla. Para comprobar que FreeAndNil funciona del mismo modo, reemplace los dos estamentos Free y nil en la interfaz de usuario (ejemplo 4.1, paso 2 lneas 57-58) con un simple estamento FreeAndNil(NewClient);. Ejecute el programa de nuevo. Si un objeto existe, el primer click sobre el botn Free displaya el estamento destroying, pero sucesivos clicks no lo harn.

Creacin y Destruccin de Objetos


Como ya hemos mencionado, dos tipos de error son importantes de considerar durante la creacin y destruccin de objetos: los cuelgues de memoria, donde la referencia es reasignada antes de que el objeto sea liberado (causando un objeto inaccesible), y una referencia colgante, donde la referencia ni est establecida a nil ni destruda antes de que el objeto al que se refiere sea liberado. Retornamos a esos errores en sta seccin a travs del uso de diagramas similares a los vistos en el captulo 3.

Cuelgues de Memoria
Asumamos que hemos declarado un objeto MyObjName de clase TMyClass:
var MyObjName: TMyClass;

Esto crea un nombre que acta como una referencia a un objeto de clase TMyClass. Inicialmente, esta referencia se establece a nil.

Aprendiendo OOP con Delphi

Pgina 8 de 29

Creado el 16/06/09

Figura 3: Una referencia a objeto inicializada a nil

Ahora creamos un objeto y lo enlazamos al nombre:


MyObjName := TmyClass.Create;

Figura 4: Creando un objeto y asignando una referencia

Cuando hemos terminado con MyObjName, lo liberamos y asignamos la referencia a nil, usando tanto:
MyObjName.Free; MyObjName := nil;

como:
FreeAndNil (MyObjName);

Si hacemos esto, retornamos a la situacin de la Figura 3, la cual es una situacin segura. Sin embargo, si simplemente creamos un nuevo objeto y asignamos el nombre a ste antes de liberar el primer objeto, tenemos la situacin en la que el enlace entre MyObjName y el primer objeto est roto, para ser reemplazado por un enlace entre MyObjName y el nuevo objeto:
MyObjName := TMyClass.Create; ... ... MyObjName := TmyClass.Create;

Figura 5: Reasignando una referencia antes de liberar el objeto anterior

Si MyObjName fue la nica referencia al primer objeto, rompiendo el enlace entre l y MyObjName significa que el primer objeto ya no est por ms tiempo accesible al programa. Si no liberamos el primer objeto y liberamos su memoria antes de reasignar MyObjName, el objeto contina residiendo (intilmente) en memoria, provocando una prdida de memoria.
Aprendiendo OOP con Delphi Pgina 9 de 29

Creado el 16/06/09

Un error similar, donde un objeto inaccesible ocupa memoria, es asignar el nombre del objeto a nil sin liberar el objeto.

Figura 6: Una referencia a nil, dejando el objeto inaccesible

Estos errores pueden ocurrir muy sutilmente. Por ejemplo, podemos crear un objeto en un manejador de evento. Si ejecutamos ese manejador repetidamente, creamos un nuevo objeto cada vez y provocamos fugas de memoria. As, antes de crear el objeto, testeamos para asegurarnos de que todava no existe (como en el ejemplo 4.1, paso 2, lnea 28). Alternativamente, liberamos el objeto antes de salir del manejador de eventos para asegurarnos de que ningn objeto exista la prxima vez que el manejador de eventos se ejecute. Si declaramos una referencia a objeto dentro de un mtodo, esa referencia desaparecer cuando el mtodo se complete, pero el objeto mismo no lo har. A menos que l tambin haya sido asignado a una referencia con un alcance ms amplio que el mtodo, ste debe ser liberado o permanecer en memoria, inaccesible y ocupando espacio, y causando una prdida de memoria.

Una Referencia Colgante


El segundo tipo de error es liberar el objeto sin establecer el nombre a nil. El nombre entonces se refiere a un objeto que ya no existe. Esto es a veces llamado referencia colgante. Es particularmente probable que ocurra cuando tenemos varias referencias al mismo objeto y no las asignemos a nil cuando liberemos el objeto.

Figura 7: Una referencia colgante

Liberando Objetos, Componentes y Formularios


Delphi tiene dos mtodos para desalojar objetos, Free y Destroy. Cuando trabajamos con objetos generados por el programador, usaremos Free con muy pocas excepciones. Esto porque Free primero chequea que un objeto ya existe. Si existe, Free llama a Destroy para desalojar el objeto. Si no existe, sale sin intentar desalojar el objeto no existente. Pero Qu pasa con los objetos generados via RAD (p.ej., los componentes)? Cuando un componente es liberado, ste libera a todos los componentes de su propiedad antes de permitrsele a l mismo destruirse. La aplicacin posee a todos los formularios, y as cuando el programa termina la aplicacin libera todos los formularios. Cada formulario libera los componentes que se encuentran dentro de l. Como consecuencia, cuando el programador instancia un componente en cdigo de programa, el constructor create debe ser pasado como parmetro para identificar al propietario (como en el ejemplo 1.3, paso 7: vea aquella discusin).

Aprendiendo OOP con Delphi

Pgina 10 de 29

Creado el 16/06/09

Cualquier objeto que el programador haya creado y todava exista cuando la aplicacin termina es desubicado automticamente. Sin embargo, si los objetos no han sido necesarios durante el tiempo de ejecucin de la aplicacin estos deberan ser liberados cuando no vayan a ser necesitados para evitar obstrucciones de memoria innecesarias. Esto se aplica particularmente a programas con largo nmero de objetos transitorios y para programas de larga duracin. Sin embargo, Tenga cuidado de no liberar un objeto o componente en uno de sus propios mtodos o manejadores de evento! Un formulario puede ser liberado explcitamente llamando a su mtodo Release. ste espera hasta que los manejadores de evento del formulario y sus componentes hayan terminado, antes de desubicar el formulario.

Ejemplo 4.3: Propiedades


Una consecuencia de proporcionar encapsulacin es que debemos escribir mtodos de acceso para proporcionar una interfaz pblica a los campos de datos. Delphi tambin nos permite usar propiedades para sta interfaz pblica. Ya hemos usado propiedades extensivamente cuando trabajamos con componentes VCL Delphi y en ste ejemplo vemos cmo crearlos para nuestras propias clases.

Ejemplo 4.3 paso 1: Definiendo Propiedades


La clase TClient en ClientU.pas tiene dos elementos de datos privados, FAccNo y FCName (lneas 67 del ejemplo 4.2, paso 1, y siguientes). Introduciremos propiedades para estos, llamadas AccNo y CName, a travs de la declaracin de tipos en ClientU.pas (lneas 1516 debajo). Movemos los mtodos Set y Get desde la seccin pblica (ejemplo 4.2, paso 1, lneas 1114) en la seccin privada de la declaracin de tipos (lneas 811 debajo). Entonces aadimos las declaraciones de propiedades a la seccin public (lneas 1516) pero no interfiere con los elementos subyacentes FaccNo o FCName. La seccin de implementacin permanece inalterada.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 TClient = class (TObject) private FAccNo: string; FCName: string; function GetAccNo: string; // access methods now private function GetCName: string; procedure SetAccNo(const AnAccNo: string); procedure SetCName(const ACName: string); public constructor Create (ACName, AnAccNo: string); destructor Destroy; override; property AccNo: string read GetAccNo write SetAccNo; property CName: string read GetCName write SetCName; end; // end TClient = class (TObject)

Una propiedad tiene visibilidad pblica (p.ej., lnea 15 anterior). Esta declaracin es introducida por la palabra clave property, seguida del nombre y tipo de la propiedad. Por convencin los nombres de propiedades son lo mismo que los nombres de datos pero sin la precedente F. Luego viene la palabra clave read seguido del mtodo para leer datos. Aqu usamos la misma funcin GetAccNo que usamos antes para suministrar el valor del campo de datos es ahora declarado como privado: lnea 8 anterior). Luego viene la palabra clave write seguido del mtodo de escritura, y usamos la misma funcin SetAccNo que utilizamos antes para modificar el valor del campo de datos FAccNo. As que usamos los mismos mtodos Get y Set de antes para mapear los mtodos con los accesos a propiedades, aunque estos mtodos son ahora frecuentemente declarados como privados.
FAccNo (GetAccNo

Aprendiendo OOP con Delphi

Pgina 11 de 29

Creado el 16/06/09

Advierta que declaramos los mtodos Get y Set y los campos de datos asociados antes de declarar las propiedades. Si no lo hacemos, el compilador no sabr de la existencia de estos datos y mtodos cuando se los encuentre en la declaracin de propiedad. Y ah estn! Hemos declarado dos propiedades.

Ejemplo 4.3 paso 2: Usando Propiedades


Ya estamos familiarizados con el uso de propiedades desde componentes RAD. Cambie las llamadas a mtodo en ObjectAccessU.pas (ejemplo 4.1, paso 2) a referencias a propiedades como sigue (lneas 5253 debajo):
45 procedure TfrmAccessObject.btnShowClick(Sender: TObject); 46 begin 47 { .. if statement as before .. } 52 lblName.Caption := 'Name: ' + NewClient.CName; // use the property 53 lblAccNo.Caption := 'Acc No: ' + NewClient.AccNo; 54 end; // end procedure TfrmAccessObject.btnShowClick

Ejecute el programa. La operacin en pantalla es como antes. En ste ejemplo vemos cmo las propiedades usan mtodos de acceso. Las propiedades proporcionan las mismas ventajas de encapsulacin que lo mtodos de acceso. Por ejemplo, si en el futuro necesitamos cambiar la representacin de los datos (privados) asociados podemos hacerlo as y entonces realizar la necesaria traslacin de tipos en los mtodos de acceso de la propiedad. La interfaz externa permanece igual y el cliente usando la propiedad sigue sin saber que ha habido un cambio. Como ya ilustramos en el ejemplo 4.4, tambin podemos hacer control de acceso, comprobacin de rango, validacin de datos, iniciacin de eventos y ms en los mtodos de acceso que necesitemos. Pero si esencialmente no hay diferencia entre usar propiedades y usar mtodos de acceso, Cul es el mejor?Por qu no quedarnos con un slo? Como veremos en el siguiente paso, bajo ciertas condiciones las propiedades nos permiten simplificar un programa mediante la supresin de los mtodos Get y Set mientras mantenemos los beneficios de la encapsulacin.

Ejemplo 4.3 paso 3: Propiedades de Acceso Directo


En el paso 1 de ste ejemplo los mapeados de mtodos simplemente proporcionaban acceso a los campos de datos (ejemplo 4.1, paso 1, lneas 26 30). Los procedimientos Set asignaban el parmetro entrante al campo de datos privado, usando un estamento simple de programa. Las funciones Get retornaban el valor del campo de datos asociado, usando tambin un nico estamento de programa (ejemplo 4.1, paso 1, lneas 18 22). Ellos no hacen manipulacin extra y los mtodos de acceso estn ah slo para proporcionar acceso pblico a los datos privados. Bajo stas circunstancias podemos usar mapeado directo de propiedades a campos de datos y prescindir de los mtodos de acceso. La creacin de propiedades con acceso directo a los datos asociados nos da una definicin de clase ms simple. Cambie la declaracin de tipo y la seccin de implementacin en ClientU.pas como sigue:
1 unit ClientU; 2 interface 3 type 4 TClient = class (TObject) 5 private 6 FAccNo: string; 7 FCName: string; 8 public

Aprendiendo OOP con Delphi

Pgina 12 de 29

Creado el 16/06/09

9 constructor Create (ACName, AnAccNo: string); 10 destructor Destroy; override; 11 property AccNo: string read FAccNo write FAccNo; 12 property CName: string read FCName write FCName; 13 end; // fin TClient = class (TObject) 14 implementation 15 { TClient } 16 { Constructor & destructor como antes } 30 { No se necesitan mtodos Get ni Set } 31 end. // fin ClientU

Y ya est. No son necesarios mtodos de acceso. En lugar de referenciar a mtodos de acceso, las declaraciones de propiedades ahora se refieren directamente a los campos de datos asociados en los mapeados de lectura y escritura (lneas 1112). Una referencia de programa a una de stas propiedades resulta en acceso directo al dato subyacente. Ya que no son necesarios mtodos de acceso ninguno es declarado en la seccin de tipo ni estn implementados en la seccin de implementacin. Ejecute y pruebe el programa. El cdigo es significativamente menor, pero el display de pantalla es exactamente el mismo que antes.

Ejemplo 4.4: Procesamiento dentro de Mtodos de Acceso


Como mencionamos en el ejemplo 4.3, paso 2, podemos realizar procesamiento adicional dentro de mtodos de acceso. Supongamos que una peticin de cambio viene a travs de esos nmeros de cliente que siempre tienen seis dgitos sin caracteres alfabticos. Esto significa que el campo de datos FAccNo ahora es declarado como un integer en lugar de un string (esto siempre ahorrar espacio de almacenamiento) y que todos los nmeros de cliente existentes con menos de seis dgitos ahora deben ir prefijados con un 1 y tantos ceros como sean necesarios para hacer nmeros de seis dgitos. Los nuevos nmeros de cliente comenzarn en 200000. Claramente queremos minimizar los cambios requeridos en el programa tanto como sea posible, y la interfaz de usuario por lo menos no debera verse afectada con cambios restringidos a la definicin de clase TClient (sta es una situacin donde la separacin de conceptos, tal como discutimos en el captulo 3, ayuda a desvincular los objetos de interfaz de usuario y los objetos de dominio o aplicacin). Cambiamos la propiedad de acceso directo AccNo de la clase TClient en el ejemplo 4.3 a un acceso mapeado y realizamos el proceso necesario dentro de los mtodos de acceso. Debido a que estamos almacenando el nmero de cuenta como un integer, el constructor convierte la cadena entrante en un integer. Por variedad, no proporcionaremos un mensaje cuando destruyamos un objeto TClient, y as podemos dejar de lado la implementacin del destructor y en su lugar basarnos en el Destroy heredado implcitamente.
1 unit ClientU; 2 interface 3 type 4 TClient = class (TObject) 5 private 6 FAccNo: integer; // Ahora un integer 7 FCName: string; 8 function GetAccNo: string;

Aprendiendo OOP con Delphi

Pgina 13 de 29

Creado el 16/06/09

9 10 11 12 13 14

procedure SetAccNo(const AnAccNo: string); public constructor Create (ACName, AnAccNo: string); // sin destructor property AccNo: string read GetAccNo write SetAccNo; // mapeado de mtodo property CName: string read FCName write FCName; // mapeado directo end; // fin TClient = class (TObject)

15 implementation 16 uses Dialogs, SysUtils; // para conversiones message & Int <-> Str 17 18 19 20 21 22 23 24 25 26 27 28 29 30 { TClient } constructor begin inherited FAccNo := FCName := end; // fin TClient.Create(ACName, AnAccNo: string); Create; // Primero invoca al constructor de la superclase StrToInt(AnAccNo); // convierte string a integer ACName; constructor Tclient.Create

function TClient.GetAccNo: string; begin if FAccNo < 100000 then Result := IntToStr(FAccNo + 100000) else Result := IntToStr(FAccNo); end; // fin function Tclient.GetAccNo

31 procedure TClient.SetAccNo(const AnAccNo: string); 32 begin 33 FAccNo := StrToInt(AnAccNo); // Cliente debe tener 6 dgitos mximo 34 end; // fin procedure Tclient.SetAccNo 35 end. // fin ClientU CName sigue siendo una propiedad mapeada directamente (lnea 14) mientras que mapeamos AccNo a travs de mtodos. AccNo est todava definido como una propiedad string para mantener la interfaz a otras unidades sin cambios (lnea 13) pero el campo de datos subyacente, FAccNo se ha vuelto un integer (lnea 6). a consecuencia de esto, el mtodo de acceso mapeado SetAccNo convierte la cadena entrante a un integer (lnea 33) (asumimos que el cliente garantiza que AnAccNo es un nmero vlido de no ms de seis dgitos y as ste mtodo no hace chequeo). Si el valor almacenado es menor de seis dgitos, GetAccNo aade 100000 al nmero antes de convertirlo en una cadena (lneas 2627).

En caso contrario convierte el valor directamente (lnea 29). La interfaz de usuario permanece sin cambios y, debido a la encapsulacin, ignora que la representacin de datos ha cambiado.

Ejemplo 4.5: Un Objeto Inmutable


Desde el ejemplo 4.2 puede parecer que un constructor es opcional. Podemos tanto usar el constructor por defecto heredado desde la superclase y luego inicializar los valores a travs de mtodos Set, como escribir un constructor especficamente para inicializar los valores. Con un constructor tenemos menos necesidad de mtodos Set, ya que los valores son inicializados a travs del constructor y ledos a travs de Getters. Sin embargo, los Setters son todava tiles para cambiar los valores de un objeto una vez haya sido inicializado.

Aprendiendo OOP con Delphi

Pgina 14 de 29

Creado el 16/06/09

A veces uno o ms campos de datos de un objeto deben ser fijos ellos deben ser establecidos cuando el objeto es construdo y entonces ellos no pueden ser cambiados. Por ejemplo, para incrementar la seguridad del sistema, puede ser importante que un nombre de cliente o nmero no puede ser cambiada una vez que el objeto TClient ha sido creado. En sta situacin queremos crear un objeto inmutable, donde inicializamos su valor en la creacin pero no establecemos campos de datos de ninguna otra forma (inmutable significa que no puede ser cambiado). En sta situacin un constructor se vuelve esencial. Como muestra el siguiente paso, podemos crear una clase inmutable estableciendo los campos de datos inmutables en el constructor del objeto y entonces proporcionando slo un mtodo de acceso Get o una propiedad de slo lectura. El destructor que introducimos en el ejemplo 4.2 lo fue para propsitos ilustrativos. Por brevedad lo eliminamos de los dems ejemplos.

Ejemplo 4.5 paso 1: Sin Mtodos Set


Si uno est usando mtodos de acceso, una clase inmutable tiene un constructor (para inicializar los campos de datos inmutables) y mtodos Get (para leer los campos de datos) en la definicin de clase pero no mtodos Set para los campos de datos inmutables. Ya que los campos de datos son privados, no es posible, en ausencia de mtodos Set, sobreescribir estos valores de campos de datos existentes desde cualquier otra unidad.
1 unit ClientU; 2 // Inmutable: sin mtodos Set 3 interface 4 type 5 TClient = class (TObject) 6 private 7 FAccNo: integer; 8 FCName: string; 9 public 10 constructor Create (ACName, AnAccNo: string); 11 function GetAccNo: string; // Only Getters, no Set methods 12 function GetCName: string; 13 end; // fin TClient = class (TObject) 14 implementation 15 uses SysUtils; // para conversiones Int <-> Str 16 { TClient } 17 constructor TClient.Create(ACName, AnAccNo: string); 18 begin 19 inherited Create; 20 FAccNo := AnAccNo; 21 FCName := ACName; 22 end; // fin constructor Tclient.Create 23 function TClient.GetAccNo: string; 24 begin 25 if FAccNo < 100000 then 26 Result := IntToStr(FAccNo + 100000) 27 else 28 Result := IntToStr(FAccNo); 29 end; // fin function Tclient.GetAccNo 30 function TClient.GetCName: string;

Aprendiendo OOP con Delphi

Pgina 15 de 29

Creado el 16/06/09

31 begin 32 Result := FCName; 33 end; // fin function Tclient.GetCName 34 end. // fin ClientU

Ejemplo 4.5 paso 2: Propiedades de Slo Lectura


Si estamos usando propiedades, creamos una clase inmutable proporcionando un constructor y propiedades de slo lectura. Nosotros creamos propiedades de slo lectura obviando el mapeado de escritura (lneas 12 y 13 debajo) (tambin podemos crear propiedades de slo escritura, no proporcionando mapeado de lectura, pero esto es menos comn). Si damos a nuestros campos de datos acceso pblico en lugar de usar propiedades, no podemos hacer una restriccin de slo lectura o slo escritura.
1 unit ClientU; 2 // Inmutable: propiedades de slo-lectura 3 interface 4 type 5 TClient = class (TObject) 6 private 7 FAccNo: integer; 8 FCName: string; 9 function GetAccNo: string; 10 public 11 constructor Create (ACName, AnAccNo: string); 12 property AccNo: string read GetAccNo; // sin mtodo de escritura 13 property CName: string read FCName; // sin mapeado de escritura 14 end; // fin TClient = class (TObject) 15 implementation 16 uses SysUtils; // para conversiones Int <-> Str 17 { TClient } 18 constructor TClient.Create(ACName, AnAccNo: string); 19 begin 20 inherited Create; 21 FAccNo := StrToInt(AnAccNo); // campos de datos establecidos slo en // constructor 22 FCName := ACName; 23 end; // fin constructor Tclient.Create 24 function TClient.GetAccNo: string; 25 begin 26 if FAccNo < 100000 then 27 Result := IntToStr(FAccNo + 100000) 28 else 29 Result := IntToStr(FAccNo); 30 end; // fin function Tclient.GetAccNo 31 end. // fin ClientU

Aprendiendo OOP con Delphi

Pgina 16 de 29

Creado el 16/06/09

Patrn 4.1: El Patrn Inmutable


Como mencionamos en la introduccin, una tendencia emergente en el desarrollo de software es capturar probadas y testeadas soluciones para problemas recurrentes como patrones. Los patrones son frecuentemente descritos muy formalmente bajo un nmero o juego de cabeceras para hacer sencillo a los usuarios de patrones con experiencia comprender un nuevo patrn. Aqu usamos una aproximacin menos formal, ya que es una aproximacin al concepto. Primero describimos el contexto general que se aplica al patrn, cul es el problema actual y los factores que entran en juego a la hora de resolver el problema. Entonces, describiremos una solucin al problema, indicando sus puntos fuertes, limitaciones y debilidades. Al final del captulo daremos referencias sobre el iniciador del patrn y posiblemente a algunas otras fuentes de informacin sobre esto. La necesidad de crear un objeto que no pueda ser cambiado ms tarde es un problema recurrente que ha sido formulado como El Patrn Inmutable (Grand, 1998, pp 6771). Podemos expresar ste patrn como sigue: A veces necesitamos un objeto sobre cuyos datos tengamos la garanta de que estos no se pueden cambiar una vez el objeto ha sido creado. Otros objetos deben ser capaces de confiar en esto completamente. Por ejemplo, en un sistema bancario, puede ser una regla de negocio el que una cuenta de cliente asignada no pueda ser modificada. Otro ejemplo es para el caso de los datos centralizados que son accedidos desde distintas partes de un programa. Donde los datos deben cambiar, es necesario crear una nueva instancia de la clase inmutable. Garantizar que los datos son inmutables ayuda siempre que sea apropiado a hacer un programa ms robusto. Una preocupacin es la de crear ese objeto, de forma que podamos aplicar sta inmutabilidad -no debe haber ninguna puerta trasera para permitir que ste valor pueda ser cambiado-. Por tanto, nosotros creamos una clase inmutable donde el constructor es el nico lugar donde los valores del objeto son establecidos. El objeto no debe tener mtodos Set ni propiedades de escritura para los datos inmutables. Los campos de datos deberan ser declarados como privados para que las subclases no puedan accederlos y ninguna otra clase debera ser declarada en la misma unit. Ninguno de los mtodos de objeto pueden modificar los datos de ninguna forma. Si el objeto inmutable usa un objeto subsidiario para almacenar el dato, ste slo puede retornar los valores de los datos o una copia del objeto subsidiario, nunca una referencia o puntero a l. La clase del ejemplo 4.5 es una clase simple de un objeto inmutable. Como una clase inmutable se vuelve ms compleja es importante asegurarse de que no hay formas indirectas de que otros objetos puedan modificar los datos. Esta cuestin de la creacin de datos fiables volver cuando nos fijemos en algunas de las diferencias entre la asociacin y la composicin.

Usando Propiedades
La Flexibilidad de las Propiedades
El uso de mapeado de acceso como el del ejemplo 4.3, paso 3, ciertamente nos acort el nmero de lneas de programa que tenamos que escribir. Lo que conseguimos a muy bajo coste con el mapeado directo de propiedades es la facilidad de uso que se obtiene con variables globales mientras mantenemos la encapsulacin. Con el mapeado directo las propiedades nos dan acceso a las variables de datos subyacentes. Pero en cualquier momento podemos simplemente cambiar la implementacin de las propiedades para usar mtodos de acceso que proporcionen la necesaria proteccin. Otras partes del programa no sern conscientes de que cualquier cambio se ha producido. Otras partes del programa que contravengan los requisitos establecidos en los mtodos de acceso podrn, sin embargo, ser bloqueadas.
Aprendiendo OOP con Delphi Pgina 17 de 29

Creado el 16/06/09

Usamos mtodos de mapeado para aquellas propiedades donde las operaciones adicionales son necesarias, adems del acceso a datos subyacentes. Estas operaciones podran implicar la validacin o podran requerir informacin o construccin de un valor. No necesitamos verificar o manipular los datos cuando usamos mapeado de acceso directo confiando en el conocimiento de que en el futuro podremos aadir mtodos de acceso si los necesitamos. Tambin podemos combinar un mtodo de mapeado y un acceso directo. Es comn, por ejemplo, usar acceso directo para leer una propiedad pero usar un mtodo de mapeado Set para escribir una propiedad. Esto hace ms simple leer un valor mientras se proporciona la oportunidad de la validacin de datos y ms en el mtodo de mapeado de escritura (Set).

Especificando una propiedad


El mapeado de propiedades en base a mtodos se especifica como sigue:
... TThisClass = class (TObject) private FData: type; procedure SetData (const AData: type); function GetData: type; public property Data: type read GetData write SetData; ... function TThisClass.GetData: type; begin // Validacin, traslacin, etc, segn se necesite Result := FData; end; procedure TThisClass.SetData(const AData: type); begin // Validacin, traslacin, etc, segn se necesite FData := AData; end;

Cuando un programa asigna un valor a una propiedad, ste valor es transferido como el parmetro AData al procedimiento Set (mtodo). El mtodo Set puede procesar ste parmetro de varias formas antes de asignar un valor a la variable FData. FData tiene mbito privado para asegurarse de que el valor de FData est estrictamente controlado. Cuando un programa usa un valor de propiedad, ste valor es transferido como el valor de retorno de la funcin Get (mtodo). El mtodo Get puede realizar cualesquiera acciones intermedias que sean necesarias y asignar un valor derivado desde la variable FData a la variable de la funcin Result en orden a retornar el valor. Los tipos de datos de la propiedad y el campo de datos subyacente son usualmente el mismo pero puede diferir si es necesario. El mapeado directo de propiedad se especifica como sigue:
... private FData: type; public property Data: type read FData write FData; ...

Aprendiendo OOP con Delphi

Pgina 18 de 29

Creado el 16/06/09

Cuando un programa asigna un valor a una propiedad mapeada directamente, ste valor es transferido directamente a la variable privada FData. Cuando un programa usa un valor de propiedad, el valor de la variable FData es transferido directamente sin procesos intermedios. Ninguna validacin o manipulacin es posible y los tipos de datos son los mismos y no pueden diferir. Si slo se especifica un mapeado de lectura (p.ej., con valores contruidos), la propiedad es de slo lectura, y viceversa, aunque el objeto mismo pueda an leer y escribir al campo de datos subyacente. Delphi proporciona una utilidad llamada Completado de Clase que puede ahorrarnos mucho tiempo en escritura de propiedades (y en la generacin de los esqueletos de los mtodos): vea la ayuda en lnea para ms detalles.

Synopsis de las Propiedades


Esta sinopsis sumariza lo que ya hemos visto en ste captulo y menciona un nmero de puntos que veremos ms claros a medida que nos movamos a travs del mdulo.

Las propiedades pueden ser accedidas mediante mtodos mapeados o acceso directo. Una propiedad puede representar a un atributo de dato (privado) o a un valor construido o modificado. Las propiedades pueden ser de lectura/escritura, slo lectura o slo escritura. Cuando procede, los accesos directos nos ahorran cdigo. Si es necesario, las propiedades de acceso directo pueden ser ms tarde extendidas a accesos mapeados (escribiendo mtodos de acceso) sin que afecte a aquellas partes del programa que ya han usado las propiedades. El mapeado de mtodos puede ser usado para construir o modificar datos, para realizar conversiones de tipos y para proporcionar control de acceso y validacin cuando la propiedad es accedida. Los valores construidos requieren un mtodo de acceso de lectura para construir el valor y tpicamente no tienen permisos de escritura (veremos esto ms tarde). Una superclase se vuelve parte de la definicin de subclase de la misma forma que lo hacen campos de datos convencionales y mtodos.

Ejemplo 4.6: Nombres de Objetos como Referencias


A lo largo de este captulo hemos estudiado la manera de acceder a los datos de objetos. En resumen, estos datos se declaran como privados y as se encapsulan: proporcionamos y controlamos el acceso a travs de mtodos y propiedades. En ste ejemplo vamos a acceder a los objetos mismos. Como ya hemos visto, un nombre de objeto es una referencia al objeto. Cada vez que usamos el nombre de un objeto, Delphi (y Java y C# tambin) automticamente de referencia el nombre para obtener la direccin del objeto. Normalmente no necesitaremos preocuparnos del dereferenciado. Pero esto se vuelve importante a veces, por ejemplo cuando creamos una asociacin entre dos objetos, y nos fijamos en algunas de las implicaciones de un modelo de referencia en ste ejemplo, que extiende el ejemplo 4.5. ste crea una serie de objetos Client, aade una referencia a cada objeto a una lista, en ste caso a un componente TListBox, y displaya los contenidos de cada objeto dereferenciando el elemento coincidente en la lista (Figura 8).

Aprendiendo OOP con Delphi

Pgina 19 de 29

Creado el 16/06/09

Figura 8: Pantalla: Aadiendo y seleccionando desde una lista de objetos

Figura 9: Los componentes que constituyen la interfaz de usuario

Puede desear editar el programa desde el ejemplo 4.5. De lo contrario, para iniciar una nueva aplicacin, construya la interfaz de aplicacin de las figuras 8 y 9, y aada la unit ClientU desde el ejemplo 4.5 al nuevo proyecto.

Ejempo 4.6 paso 1: Creando y Aadiendo Objetos a una Lista


Usaremos el manejador de evento OnClick del botn btnCreate para crear un objeto de tipo TClient (lnea 38 debajo) y luego aadiremos una referencia a ese objeto al ListBox (lnea 40).
1 unit ObjAccessU; 2 interface 3 { Standard RAD interface declaration } 22 implementation 23 uses ClientU; // Move from global uses clause 24 {$R *.dfm} 25 26 27 28 29 30 31 32 33 34 35 {TfrmAccessObject} procedure TfrmAccessObject.btnCreateClick(Sender: TObject); var NewClient: TClient; Dummy: integer; begin // Sale si la entrada es invlida if (edtCName.Text = '') or (not TryStrToInt(edtAccNo.Text, Dummy)) then begin ShowMessage ('Por favor, introduzca un nombre y/o nmero vlidos');

Aprendiendo OOP con Delphi

Pgina 20 de 29

Creado el 16/06/09

36 37 38

Exit; end; NewClient := TClient.Create (edtCName.Text, edtAccNo.Text);

39 // Aade el objeto al ListBox y crea referencia adicional 40 lstClients.AddItem(NewClient.CName, NewClient); 41 edtCName.Clear; 42 edtAccNo.Clear; 43 edtCName.SetFocus; 44 end; // fin procedure TfrmAccessObject.btnAddClick

Advierta que la referencia NewClient ya no es ms parte de la interfaz de usuario (como en el ejemplo 4.1, paso 2, lneas 1819) y en su lugar es declarada localmente en el manejador de eventos (lneas 2728 anteriores). Esto tambin significa que la referencia a la clusula global uses para ClientU (ejemplo 4.1, paso 2, lnea 5) puede moverse a una clusula uses local (lnea 23 arriba), restringidiendo as la visibilidad en ClientU. Mencionamos antes que TClient asume que los valores de inicializacin que ste recibe de CName y son vlidos. La interfaz de usuario por tanto toma la responsabilidad de comprobar que el valor introducido por el usuario para el nombre del cliente no es una cadena vaca (lnea 32) y que el nmero de cuenta es un integer vlildo (lnea 33).
AccNo

La lnea 40 introduce un mtodo ListBox, AddItem, que puede parecer nuevo. Frecuentemente usamos el mtodo Add para aadir un string a un ListBox. AddItem nos permite aadir un string y una referencia a objeto asociado a un ListBox. Si consulta la ayuda en lnea ver que es declarado como:
procedure AddItem(Item: String; AObject: TObject);

As en la lnea 40 aadimos el nombre del cliente como un string al ListBox y adems aadimos una referencia al objeto que acabamos de crear pasando el nombre que hemos asignado en la lnea 38. Usaremos sta referencia a objeto en el siguiente paso. Cuando ste mtodo termina, pierde la referencia NewClient ya que sta es una variable declarada localmente (lneas 2728). La nica referencia que tenemos ahora a ste objeto es la referencia almacenada en el ListBox mediante la lnea 40.

Ejemplo 4.6 paso 2: Referenciando el Objeto


Ahora necesitamos encontrar la manera de primero identificar un objeto determinado del ListBox y luego acceder al objeto. Hacemos esto aadiendo un manejador de evento OnClick para el ListBox como sigue:
45 procedure TfrmAccessObject.lstClientsClick(Sender: TObject); 46 var 47 CurrentClient: TClient; 48 begin 49 lblIndex.Caption := 'Item index: ' + 50 IntToStr (lstClients.ItemIndex); 51 52 CurrentClient := lstClients.Items.Objects [lstClients.ItemIndex] as Tclient;

53 lblName.Caption := 'Name: ' + CurrentClient.CName; 54 lblAccNo.Caption := 'Acc No: ' + CurrentClient.AccNo; 55 end; // fin procedure TfrmAccessObject.lstClientsClick

Aprendiendo OOP con Delphi

Pgina 21 de 29

Creado el 16/06/09

Cada siguiente elemento en el ListBox tiene un ndice (comenzando desde 0). Cuando el usuario hace click sobre un elemento, establece la propiedad ItemIndex del ListBox al valor del ndice del elemento seleccionado y entonces lanza el evento OnClick. El primer estamento de programa en el manejador de eventos (lneas 4950) displaya el valor de ItemIndex para el elemento seleccionado. El siguiente estamento (lneas 5152) crea una referencia al objeto asociado. ste primero identifica al objeto usando la propiedad ItemIndex, luego lo convierte a TClient, y finalmente lo asigna a la variable declarada localmente CurrentClient (lneas 4647). Los siguientes dos estamentos de programa usan sta referencia declarada localmente para displayar los valores de las propiedades requeridas. Despus de esto el manejador de eventos termina y la referencia CurrentClient desaparece. As que no es necesario enviar a nil la referencia. No debera ser liberada definitivamente porque la referencia al mismo objeto en el ListBox podra volverse una referencia colgante.

Ejemplo 4.6 paso 3: Borrando un Objeto de la Lista


Cuando necesitemos eliminar un objeto de la lista, usaremos la referencia al objeto en el ListBox (lneas 6970 debajo) para liberar el objeto (lnea 71) y entonces destruir la referencia eliminndola del ListBox (lnea 72).
56 procedure TfrmAccessObject.btnDeleteClick(Sender: TObject); 57 var 58 CurrentClient: TClient; 59 begin 60 if lstClients.ItemIndex < 0 then 61 begin 62 ShowMessage ('No client selected'); 63 exit; 64 end; 65 66 67 if MessageDlg ('Delete selected client?', mtConfirmation, mbOkCancel, 0) <> mrOK then // confirm deletion exit;

68 // OK to delete so go ahead 69 CurrentClient := lstClients.Items.Objects [lstClients.ItemIndex] 70 as TClient; 71 FreeAndNil(CurrentClient); // or CurrentClient.Free; 72 lstClients.DeleteSelected; 73 lblIndex.Caption := 'Item index'; // update display 74 lblName.Caption := 'Name'; 75 lblAccNo.Caption := 'Acc No'; 76 end; // end procedure TfrmAccessObject.btnDeleteClick 77 end. // end unit ObjAccessU

Los conceptos cubiertos aqu son similares a los que usaremos para crear enlaces de asociacin ms tarde en ste mdulo, as que los volveremos a ver. Introduzca el cdigo de estos tres pasos, almacene el programa y ejectelo. Juegue con l para entender cmo funciona.

Referenciando Objetos a travs de un TListBox


Por conveniencia, sumarizamos las operaciones con el ListBox que hemos utilizado en ste ejemplo.

Aprendiendo OOP con Delphi

Pgina 22 de 29

Creado el 16/06/09

Aadiendo una Referencia a un TlistBox


Utilice el mtodo AddItem del TListBox, suministrando el texto a ser displayado en el ListBox y una referencia al objeto relacionado:
procedure TfrmAccessObject.btnCreateClick(Sender: TObject); var NewClient: TClient; begin ... lstClients.AddItem(NewClient.CName, NewClient); ... end;

Accediendo a un Objeto a travs de la Entrada del TListBox


TObject y

El mtodo Items.Objects del TlistBox retorna la referencia a una entrada indexada. Lo trata como un as lo podemos convertir a la clase apropiada:
procedure TfrmAccessObject.lstClientsClick(Sender: TObject); var CurrentClient: TClient; begin ... CurrentClient := lstClients.Items.Objects [lstClients.ItemIndex] as TClient; lblName.Caption := 'Name: ' + CurrentClient.CName; ... end; // fin procedure TfrmAccessObject.lstClientsClick

Borrando una Entrada en un TListBox


Si sta es la nica referencia al objeto referenciado por la entrada del TListBox, ste objeto debe ser liberado antes de que la propia entrada sea eliminada. No liberar el objeto da lugar a fugas de memoria. No eliminar la entrada del ListBox conduce a una referencia colgada.
procedure TfrmAccessObject.btnDeleteClick(Sender: TObject); var CurrentClient: TClient; begin ... CurrentClient := lstClients.Items.Objects [lstClients.ItemIndex] as TClient; FreeAndNil(CurrentClient); // or CurrentClient.Free; lstClients.DeleteSelected; ... end;

Sumario del Captulo


Puntos principales: 1. Creacin y liberacin de un objeto: constructores y destructores. 2. Mtodos de acceso a datos : Get y Set. 3. Propiedades para acceso a datos: propiedades mapeadas y propiedades directas, especificaciones de lectura y escritura. 4. El concepto de Patrones en la orientacin a objetos.
Aprendiendo OOP con Delphi Pgina 23 de 29

Creado el 16/06/09

5. El Patrn Inmutable. 6. El nombre del objeto como referencia: fugas de memoria y referencias colgadas. 7. Creando una lista de referencias a objetos.

Objetos como entidades independientes: Encapsulacin: mtodos de acceso y propiedades; referencias a objetos. Patrones: Inmutable.

Aprendiendo OOP con Delphi

Pgina 24 de 29

Creado el 16/06/09

Problemas
Problema 4.1. Estudio de Captulo 4
Identifique los apropiados ejemplos o secciones del captulo para ilustrar cada comentario hecho en el sumario del captulo 4 anterior.

Problema 4.2. Diferencias entre Cdigo RAD y de Programador


Varias operaciones son necesarias para usar un objeto: la clase debe ser definida, la referencia al objeto debe ser creada, y un objeto debe ser instanciado y asignado a la referencia. El objeto est ahora listo para ser usado. Una vez el programa no necesita ms el objeto, ste debera ser destrudo. Estas operaciones son necesarias tanto para los objetos generados mediante RAD y objetos generados por el programador. Describa brevemente cmo cada uno de esos cuatro pasos se realizan en Delphi tanto para objetos RAD como para los generados por el programador, referenciando en cada caso a apropiados ejemplos en las notas del mdulo. Identifique tambin las circunstancias tpicas para una pequea aplicacin Delphi bajo la cual los objetos generados por RAD o programacin son utilizados.

Problema 4.3. Un Semforo Orientado a Objetos


El cdigo para un semforo OO que funciona en una nica direccin se muestra debajo. Cambie la comunicacin del objeto en sta versin para hacer uso de propiedades, como se describe tras el listado de programa.
1 unit TrafficLightU; 2 interface 3 uses Graphics; // para TColor 4 type 5 TTrafficLight = class (TObject) 6 public 7 procedure NextState (var State: string; out Period: integer; 8 var StopLight, CautionLight, GoLight: TColor); 9 end; 10 implementation 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 { TTrafficLight } procedure TTrafficLight.NextState(var State: string; out Period: integer; var StopLight, CautionLight, GoLight: TColor); begin if State = 'Stop' then begin Period := 3000; State := 'Go'; StopLight := clBlack; GoLight := clGreen; end else if State = 'Go' then begin Period := 1000; State := 'Caution'; GoLight := clBlack; CautionLight := clYellow; end else

Aprendiendo OOP con Delphi

Pgina 25 de 29

Creado el 16/06/09

31 begin 32 Period := 4000; 33 State := 'Stop'; 34 CautionLight := clBlack; 35 StopLight := clRed; 36 end; 37 end; // fin procedure TTrafficLight.NextState 38 end. // fin unit TrafficLightU

La interfaz de usuario se muestra en las figuras 10 y 11. El programa cambia de color automticamente en la siguiente secuencia de colores: 4 segundos rojo, 3 segundos verde, 1 segundo amarillo. Las presentaciones de colores son creados mediante componentes TShape.

Figura 10: El semforo mostrando amarillo

Figura 11: Componentes de interfaz de usuario para el semforo

1 unit LightControlU; 2 interface 3 uses 4 Windows, Messages, SysUtils, Variants, Classes, Graphics, 5 Controls, Forms, Dialogs, ExtCtrls, StdCtrls, 6 TrafficLightU; 7 type 8 TfrmTrafficLight = class(TForm) 9 tmrTrafficLight: TTimer; 10 lblTrafficLight: TLabel; 11 shpRed: TShape; 12 shpYellow: TShape; 13 shpGreen: TShape; 14 procedure tmrTrafficLightTimer(Sender: TObject); 15 procedure FormShow(Sender: TObject); 16 private 17 MyTrafficLight: TTrafficLight; 18 Period: integer; 19 State: string; 20 StopLight, CautionLight, GoLight: TColor; 21 procedure UpDateDisplay; 22 end; 23 var 24 frmTrafficLight: TfrmTrafficLight;

Aprendiendo OOP con Delphi

Pgina 26 de 29

Creado el 16/06/09

25 implementation 26 {$R *.dfm} 27 procedure TfrmTrafficLight.tmrTrafficLightTimer(Sender: TObject); 28 begin 29 MyTrafficLight.NextState(State, Period, 30 StopLight, CautionLight, GoLight); 31 UpDateDisplay; 32 end; // fin procedure TfrmTrafficLight.tmrTrafficLightTimer 33 procedure TfrmTrafficLight.FormShow(Sender: TObject); 34 begin 35 MyTrafficLight := TTrafficLight.Create; 36 State := 'Caution'; 37 MyTrafficLight.NextState(State, Period, 38 StopLight, CautionLight, GoLight); 39 UpDateDisplay; 40 tmrTrafficLight.Enabled := True; 41 end; // fin procedure TfrmTrafficLight.FormShow 42 procedure TfrmTrafficLight.UpDateDisplay; 43 begin 44 tmrTrafficLight.Interval := Period; 45 lblTrafficLight.Caption := State; 46 shpRed.Brush.Color := StopLight; 47 shpYellow.Brush.Color := CautionLight; 48 shpGreen.Brush.Color := GoLight; 49 end; // fin procedure TfrmTrafficLight.UpDateDisplay 50 end. // fin unit LightControlU TfrmTrafficLight tiene cinco campos de datos privados para ser usados como parmetros en la comunicacin el mtodo NextState de TTrafficLight. Convierta estos cinco campos de datos en propiedades y luego cambie el mtodo NextState de TtrafficLight para acceder a stas propiedades en lugar de usar estos parmetros.

AVISO: cambie el mtodo NextState para que su llamada use slo un parmetro identificando el objeto llamado. Esto significa que la declaracin de mtodo en la lnea 7 de la unit TrafficLightU debe cambiar a:
procedure NextState (AClient: Tform);

Problema 4.4. Atrapando Referencias a Objetos no Existentes


En el ejemplo 4.1 usamos estamentos condicionales para atrapar una referencia a un objeto que no existe, de la forma siguiente:
if ObjectRef = nil then ShowMessage ('Error') else ObjectRef.Method;

Una alternativa ampliamente utilizada es el manejo de excepciones:


try ObjectRef.Method; except ShowMessage ('Error'); end;

Recodifique el ejemplo 4.1 para usar manejo de excepciones en lugar de estamentos condicionales donde sea apropiado.
Aprendiendo OOP con Delphi Pgina 27 de 29

Creado el 16/06/09

Problema 4.5. Herencia para Reutilizacin


Un equipo de bilogos trabajando cerca de la frontera entre Mpumalanga-Swazilandia est investigando la distribucin de una rara protea (una flor) por el sur de frica, la protea curvata. Tambin estn investigando la distribucin del crabo de franjas y el mochuelo perlado (aves) en la misma rea. Tienen planes para investigar el estado de ciertos insectos y serpientes en la misma zona en el futuro, pero los detalles de esos proyectos de investigacin slo finalizarn una vez se disponga de fondos. Un pequeo programa de ordenador sera de gran utilidad para ste equipo de investigadores, y es nuestro trabajo escribir el primer prototipo basado en las pantallas que vemos debajo. Este programa debe ser sencillo de ampliar y debe incorporar tcnicas de diseo orientadas a objetos. La pantalla de apertura permite al usuario seleccionar la operacin requerida (Figura 12):

Figura 12: El formulario principal del programa de avistamientos

Seleccionamos introducir un nuevo avistamiento de ave, presionando el RadioButton Bird en el formulario principal (Figura 12), cumplimentando la entrada (Figura 13):

Figura 13: Capturando la informacin del ave

La seleccin de la opcin de introducir un nuevo avistamiento de planta desde el formulario principal nos lleva al formulario de entrada de plantas (Figura 14): Picando tanto en el botn Capture capture los datos, cierre el formulario e inserte una entrada en el del formulario principal. Picando en el botn Close nos lleva a un cuadro de dilogo donde el usuario puede capturar la informacin o descartarla antes de cerrar el formulario (fig 15):
ListBox

Aprendiendo OOP con Delphi

Pgina 28 de 29

Creado el 16/06/09

Figura 15: Opciones al pulsar el botn de cerrar

Figura 14: Capturando informacin de la planta

En el formulario principal, seleccionando una de las entradas en el ListBox y luego pulsando sobre el botn Show Info muestra los valores de los campos de datos para ese avistamiento (Figura 16):

Figura 16: Mostrando la informacin capturada

Pulsando sobre el botn de ayuda de cualquier formulario aparece el mensaje No hay ayuda an para sta pantalla. Pulsando Close sobre cualquier ventana se cierra sta, con la opcin de decidir si capturar o no la entrada en las pantallas de entrada de datos. Los datos a ser capturados para los avistamientos de plantas son el tipo de avistamiento (Protea Curvata), la marca de zona (un string) y el nmero de individuos en la marca (un integer). A estos campos el avistamiento de un ave aade un campo indicativo de descubrimiento de campo de cra (boolean) y la fecha del avistamiento (un string). Para ste problema debe: 1. escribir el programa especificado arriba, 2. Dibujar un diagrama de clase para mostrar la estructura y relaciones de la interfaz de usuario y los modelos de dominio de clases, y 3. Dibujar un diagrama de secuencia mostrando las interacciones importantes. Puede que encuentre necesario realizar varias iteraciones entre el propio programa y los diagramas UML al escribir ste programa, a fin de obtener un grado adecuado de encapsulacin, la generalizacin / especializacin, etc.

Aprendiendo OOP con Delphi

Pgina 29 de 29