Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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
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.
Pgina 2 de 29
Creado el 16/06/09
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.
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.
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;
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.
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;
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.
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.
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).
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.
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.
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.
Pgina 8 de 29
Creado el 16/06/09
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;
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.
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.
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.
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
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.
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.
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.
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.
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.
Pgina 15 de 29
Creado el 16/06/09
31 begin 32 Result := FCName; 33 end; // fin function Tclient.GetCName 34 end. // fin ClientU
Pgina 16 de 29
Creado el 16/06/09
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).
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; ...
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.
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.
Pgina 19 de 29
Creado el 16/06/09
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.
Pgina 20 de 29
Creado el 16/06/09
36 37 38
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.
53 lblName.Caption := 'Name: ' + CurrentClient.CName; 54 lblAccNo.Caption := 'Acc No: ' + CurrentClient.AccNo; 55 end; // fin procedure TfrmAccessObject.lstClientsClick
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.
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.
Pgina 22 de 29
Creado el 16/06/09
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
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.
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.
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.
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;
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);
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
Seleccionamos introducir un nuevo avistamiento de ave, presionando el RadioButton Bird en el formulario principal (Figura 12), cumplimentando la entrada (Figura 13):
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
Pgina 28 de 29
Creado el 16/06/09
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):
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.
Pgina 29 de 29