Delphi OOP-Cap 03
Delphi OOP-Cap 03
3 Clases y
Objetos definidos por el Programador
Conceptos Principales:
Manteniendo separados la interfaz de usuario y los objetos de la aplicacin: separacin de capacidades (SoC) Imitacin de objetos de interfaz de usuario RAD en clases de aplicacin definidas por el programador Pasos a seguir para el uso de un objeto definido por el programador:
Herencia: derivando una nueva clase desde una existente Empaquetando una clase en una unit El Explorador de Cdigo y el Navegador de Proyecto Disposicin de objetos en memoria
Pgina 1 de 28
Creado el 16/06/09
Introduccin
Una forma de hacer un programa de ordenador ms robusto y ms susceptible a cambios es construirlo en capas. Uno podra tener una capa de interfaz que se ocupe de la interaccin entre el usuario y el programa, una capa de aplicacin que implemente las operaciones requeridas por la aplicacin, y una capa de persistencia que se ocupe del almacenamiento permanente de los datos. Este enfoque es a veces llamado el Modelo de las Tres Capas y es una implementacin de un principio llamado Principio de Separacin de Capacidades. El generador GUI de Delphi gestiona la capa de interfaz de usuario muy efectivamente. Pero Qu pasa con la capa de aplicacin? Para empezar nos fijaremos en la definicin e instanciacin de nuestras propias clases para proporcionar lgica de aplicacin. Mientras que es posible aadir lgica de aplicacin a los objetos de interfaz de usuarios generados mediante RAD, es una buena prctica de programacin en su lugar separar la interfaz de usuario de la de aplicacin, y es desde sta perspectiva desde la cual desarrollaremos nuestras propias clases.
Pgina 2 de 28
Creado el 16/06/09
Figura 2: Componentes en la interfaz de Figura 1: Interfaz de usuario para contar elementos usuario
La interfaz de usuario aparece en la Figura 1 y el Object Tree View, el cual est disponible en Delphi 6 y 7 y que muestra los componentes en el formulario, aparece en la Figura 2 (Delphi 8 tiene el Model View, el cual es ms sofisticado). Vamos a desarrollar el programa por etapas para ayudar a aclarar algunas de las importantes caractersticas de programacin OO. En primer lugar vamos a crear un programa no-OO, donde usamos una simple variable para mantener un registro del nmero de artculos. Luego haremos la parte de conteo de elementos del formulario, lo que nos llevar a un objeto combinado interfaz/aplicacin. Finalmente, crearemos un objeto aplicacin separado del objeto de interfaz de usuario.
Pgina 3 de 28
Creado el 16/06/09
8 gpbItems: TGroupBox; 9 lblTotal: TLabel; 10 bmbReset: TBitBtn; 11 btnAddItem: TButton; 12 btnDisplay: TButton; 13 procedure bmbResetClick(Sender: TObject); 14 procedure btnAddItemClick(Sender: TObject); 15 procedure btnDisplayMouseDown(Sender: TObject; 16 Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 17 procedure btnDisplayMouseUp(Sender: TObject; 18 Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 19 private 20 {Count: integer; // for step 2} 21 end; // end TfrmCount = class(TForm) 22 var 23 frmCount: TfrmCount; 24 implementation 25 {$R *.dfm} 26 var 27 Count: integer = 0; 28 procedure TfrmCount.btnAddItemClick(Sender: TObject); 29 begin 30 Inc (Count); 31 end; // end procedure TfrmCount.btnAddItemClick 32 procedure TfrmCount.btnDisplayMouseDown(Sender: TObject; 33 Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 34 begin 35 lblTotal.Caption := IntToStr (Count); 36 end; // end procedure TfrmCount.btnDisplayMouseDown 37 procedure TfrmCount.btnDisplayMouseUp(Sender: TObject; 38 Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 39 begin 40 lblTotal.Caption := ''; 41 end; // end procedure TfrmCount.btnDisplayMouseUp 42 procedure TfrmCount.bmbResetClick(Sender: TObject); 43 begin 44 Count := 0; 45 end; // end procedure TfrmCount.bmbResetClick 46 end. // end unit GlobalU
Este es un simple programa que funciona. Sin embargo, tiene un problema potencial. Debido a que existen tres procedimientos distintos, y los manejadores de eventos btnItemsClick, btnDisplayMouseDown y bmbResetClick se refieren todos al valor de Count, Count debe ser una variable a nivel de unidad (lneas 2627). El hecho de dar a las variables mbito de nivel de unidad no es generalmente una buena idea, y normalmente es mejor evitar hacerlo1. As que en el prximo paso haremos a sta variable parte del objeto de interfaz de usuario. Antes de pasar al siguiente paso, advierta que usamos la expresin Inc(Count) y no Count:=Count+1 en la lnea 30. Aunque funcionan ambas, Inc(Count) genera cdigo ms optimizado y es algo ms rpido y especialmente til en bucles.
1 En Delphi una variable de nivel de unidad es efectivamente una variable de clase. Si varios objetos son creados desde una clase con una variable de nivel de unidad, la misma variable de nivel de unidad est disponible para todos los objetos. Cada objeto comparte la variable de nivel de unidad: ninguno de los objetos individuales tienen su popia variable.
Pgina 4 de 28
Creado el 16/06/09
Pgina 5 de 28
Creado el 16/06/09
btnDisplay: TButton; bmbReset: TBitBtn; procedure btnItemsClick(Sender: TObject); // mtodos procedure btnDisplayMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure btnDisplayMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure bmbResetClick(Sender: TObject); private // especificador de visibilidad { declaraciones Privadas } public { declaraciones Pblicas } end;
Definiremos una clase aplicacin, TItem, para registrar el nmero de elementos del problema anterior. Desde el ejemplo 3.1, vimos que TItem necesitar un campo de datos (privado), al que llamaremos Count, y mtodos (pblicos) para aadir 1 a Count (un procedimiento), para obtener el valor de Count (una funcin) y para poner a cero el valor de Count (un procedimiento). La Figura 3 representa esto en un diagrama de clase.
Debido a que la orientacin a objetos es encapsulacin y mantenimiento de datos y funcionamiento de las clases privadas, creamos sta nueva clase en una unidad separada. As la unidad existente que acompaa a frmCount contendr la interfaz de usuario y comunicar con la unit separada que contiene el objeto que est contando los elementos. Abra el archivo de proyecto del ejemplo 3.1. Almacene el fichero unit como OODriverU. Almacene el fichero de proyecto como OODriverP. Necesitamos una segunda unit (sin formulario) para la definicin de la clase TItem. Seleccione File | New | Unit desde el men2. Esta unit no tiene formulario y Delphi proporciona slo el nombre de la unit y el esqueleto para las secciones de interfaz e implementacin:
unit Unit2; interface implementation end.
Con clases RAD Delphi genera la definicin de clases y el esqueleto para cada mtodo. Cuando creamos nuestras propias clases podemos hacer esto manualmente. Aadimos la declaracin de clase TItem a la seccin interface y sus tres mtodos (AddItem, GetCount y ZeroCount) a la seccin implementation. Lo almacenamos como ItemU.pas
1 unit ItemU;
2 La secuencia de adicin de units vara entre versiones de Delphi. D8 ?? **
Pgina 6 de 28
Creado el 16/06/09
2 interface 3 type 4 TItem = class(TObject) // derivado desde TObject 5 private 6 FCount: integer; // el dato es normalmente privado 7 public 8 procedure AddItem; // mtodos de acceso pblicos 9 function GetCount: integer; 10 procedure ZeroCount; 11 end; // fin de TItem = class(TObject) 12 implementation 13 14 15 16 17 { TItem } procedure TItem.AddItem; begin Inc (FCount); end; // fin de procedure TItem.AddItem
18 function TItem.GetCount: integer; 19 begin 20 Result := FCount; 21 end; // fin de function Titem.GetCount 22 procedure TItem.ZeroCount; 23 begin 24 FCount := 0; 25 end; // fin de procedure Titem.ZeroCount 26 end. // fin de unit ItemU
Las declaraciones de tipo (lneas 311) son como la declaracin automtica de formularios de Delphi. La lnea 4 establece que estamos definiendo una nueva clase, TItem, la cual es derivada desde TObject, la clase base de Delphi. Los objetos TItem tendrn un campo de datos, FCount de tipo integer (lnea 6), el cual hemos hecho privado para sta unit (lnea 5).
TItem tiene tres mtodos, todos los cuales son pblicos (lnea 7) y por ello estn disponibles para el resto de units de ste proyecto. Estos mtodos pblicos proporcionan al resto de objetos una forma de interactuar con el campo de datos privado, y por ello son llamados mtodos de acceso. La lnea 8 establece que TItem tiene un tipo de procedimiento llamado AddItem que no tiene parmetros. La lnea 9 declara un mtodo tipo funcin, GetCount, que no tiene parmetros de entrada y que proporciona un valor integer (un mtodo de acceso de ste formulario es a veces llamado un mtodo Getter). La lnea 10 declara otro mtodo tipo procedimiento, ZeroCount, que no tiene parmetros de entrada.
Los mtodos estn definidos en la seccin de implementacin (lneas 12-26). Debido a que el procedimiento AddItem es uno de los mtodos de TItem, y por tanto es parte de TItem, usamos notacin de puntos para incluir la relacin con TItem en la cabecera del procedimiento (lnea 14). la lnea 16 incrementa el valor de la variable (privada) FCount declarada en la lnea 6 en uno. El mtodo ZeroCount (lneas 2225), es similar. No tiene parmetros de entrada y establece el valor de a 0. Advierta que FCount no est declarado como variable local ni siquiera en la funcin. Se declara como parte de la clase TItem en la lnea 6 y ambos mtodos manipulan ste valor. Sin embargo, debido a que FCount es declarado como privado (lneas 56), otras units no pueden acceder a Fcount.
FCount
El mtodo que queda, la funcin GetCount, retorna el valor de FCount a travs de la variable (automtica) Result (lnea 20). La nica forma en que un agente externo podra manipular el valor de FCount es a travs de los dos mtodos pblicos AddItems y ZeroCount. La nica forma en que un agente externo puede acceder a ste valor es a travs del mtodo pblico GetCount.
Pgina 7 de 28
Creado el 16/06/09
Advierta que cambiamos el nombre de Count a FCount. Debido a una convencin de Delphi, los campos de datos de clases son precedidos por F para proporcionar una rpida pista visual de que esos son campos de datos locales, y para distinguirlos de las propiedades, las cuales no tienen el cartacter precedente F. Veremos ms sobre esto en el captulo 4, donde introduciremos las propiedades y veremos las relaciones entre una propiedad y un campo de datos privado. Resumiendo. Nuestro dato, FCount, es ahora pivado y est encapsulado (lneas 5-6). la nica manera de que otra unit pueda acceder a sus datos es a travs de los mtodos de acceso pblicos AddItem, GetCount y ZeroCount (lneas 711), los cuales definen estrictamente qu operaciones pueden ser realizadas sobre el dato privado. Para reforzar que ahora tenemos dos units separadas en el proyecto, vea la clusula uses en el fichero de proyecto (secuencia de men View | Units | OODriverP). Vemos que slo un form es creado , ya que la unit ItemU.pas no posee un formulario que la acompae.
program OODriverP; uses Forms, OODriverU in 'OODriverU.pas' {frmCount}, ItemU in 'ItemU .pas'; {$R *.res} begin Application.Initialize; Application.CreateForm(TfrmCount, frmCount); Application.Run; end.
Aada una referencia a la otra unit (lneas 2526 debajo) porque sta unit usa una clase (TItem) que est definida en la unit ItemU. Podemos teclear sta referencia directamente o decirle a Delphi que lo haga por nosotros, a travs de la secuencia de men File | Use Unit. Declare una referencia al objeto el cual vamos a crear (ItemCount, lneas 2728). Cree el objeto en la seccin de inicializacin de OODriverU (lneas 4748). Cuando usamos componentes, Delphi crea los objetos automticamente. Sin embargo, si definimos una clase por nosotros mismos, tal como TItem, debemos crear a mano un objeto para esta clase antes de que podamos usarlo. Aqu es conveniente crear ste objeto (ItemCount) en la seccin de inicializacin, de forma que exista al mismo tiempo que aparece el formulario. Para hacer esto usaremos el mtodo constructor Create, que TItem hereda de TObject (lnea 48) (construiremos nuestro propio constructor en el captulo 5).
Pgina 8 de 28
Creado el 16/06/09
Use el objeto en los manejadores de evento OnClick a travs de las llamadas a sus mtodos. Dependiendo de la operacin que necesitemos, llamaremos al mtodo AddItem (lnea 31) o al mtodo ZeroCount (lnea 45). usaremos el mtodo GetCount, que suministra un integer, para construir el Caption del Label (lnea 36).
23 implementation 24 {$R *.dfm} 25 uses 26 ItemU ; 27 var 28 ItemCount: Titem; 29 procedure TfrmCount.btnItemsClick(Sender: TObject); 30 begin 31 ItemCount.AddItem; 32 end; // fin de procedure TfrmCount.btnItemsClick 33 procedure TfrmCount.btnDisplayMouseDown(Sender: TObject; 34 Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 35 begin 36 lblTotal.Caption := IntToStr(ItemCount.GetCount); 37 end; // fin de procedure TfrmCount.btnDisplayMouseDown 38 procedure TfrmCount.btnDisplayMouseUp(Sender: TObject; 39 Button: TMouseButton; Shift: TShiftState; X, Y: Integer); 40 begin 41 lblTotal.Caption := ''; 42 end; // fin de procedure TfrmCount.btnDisplayMouseUp 43 procedure TfrmCount.bmbResetClick(Sender: TObject); 44 begin 45 ItemCount.ZeroCount; 46 end; // fin de procedure TfrmCount.bmResetClick 47 initialization 48 ItemCount := Titem.Create; 49 end. // fin de unit OODriverU
El constructor TItem.Create, llamado en la lnea 48, reserva memoria para mantener el objeto, inicializa los campos de datos del objeto y retorna la direccin del objeto que acaba de crear. As es como tpicamente creamos el objeto y asignamos el valor de retorno al nombre que que habamos declarado (la referencia) en un programa de una nica fase. Esto crea tanto el objeto como el enlace entre el nombre del objeto y el objeto mismo:
ItemCount := TItem.Create;
Advierta que creamos (instanciamos) un objeto desde una clase. As, cuando creamos un objeto llamamos al mtodo de clase Create (TItem.Create, lnea 48) y lo asignamos al nombre objeto declarado en la declaracin de variable (lnea 28). Cuando usamos un objeto, una vez que ha sido creado, las llamadas de mtodo son para ese objeto (ItemCount.AddItem en lnea 31, ItemCount.GetCount en linea 36 y ItemCount.ZeroCount en lnea 45, y no TItem.AddItem, etc). Almacene todo el cdigo del ejemplo 3.2. Ejecute y testee el programa.
Pgina 9 de 28
Creado el 16/06/09
Si desea saber (tracear) lo que est pasando en el programa, vuelva al editor y coloque puntos de ruptura en los cuerpos de los manejadores de evento OnClick (lneas 31, 36 y 45 en OODriverU.pas) (Para colocar un punto de ruptura, coloque el cursor en la lnea deseada y pulse <F5>). Ejecute de nuevo el programa. Pulse sobre los distintos botones, y ejecute el programa lnea a lnea usando <F7> y vea cmo la ejecucin se mueve entre las units. Recupere el funcionamiento normal mediante <F9>.
Un diagrama de secuencia contiene operaciones simples entre el usuario y estas dos clases (Figura 5). Ya que estas interacciones son flujos de mensajes y no de datos, no mostramos el dato retornado.
Pgina 10 de 28
Creado el 16/06/09
Para lograr la encapsulacin, declaramos los datos en una clase como privados. Si hacemos los datos pblicos, estos pueden ser accedidos desde el exterior del objeto y el estamento anterior funcionar, lo cual niega completamente los beneficios de la encapsulacin.
TItem = class(TObject) public FCount: integer; // WRONG! public data, so lose encapsulation procedure AddItem; function GetCount: integer; procedure ZeroCount; end; // end TItem = class(TObject)
Pgina 11 de 28
Creado el 16/06/09
En lugar del estamento Inc(ItemCount.FCount) enviaremos el mensaje AddItem al objeto ItemCount (lnea 31). La codificacin por separado de sta clase introduce una separacin de capacidades entre la interfaz de usuario y la lgica de la aplicacin. La interfaz de usuario ahora simplemente especifica qu necesita ser hecho, aadir un elemento, sin que le concierna el cmo hacerlo. El objeto ItemCount simplemente sabe que cuando reciba un mensaje AddItem, debe incrementar el valor de FCount. No necesita saber nada sobre el evento que lanz el mensaje AddItems.
Primero definimos la estructura de la clase y la implementacin de los mtodos (como gua, podemos imitar la forma en que Delphi define el form en un fichero unit). Segundo, reservamos un nombre (el cual acta como su referencia) para un objeto de clase listndolo en la declaracin de variables de la unit que va a usar el objeto (y no en la unit donde se define la clase, como hace el RAD). Tercero, activamente instrumos a Delphi para crear el objeto y asignarlo al nombre que hemos declarado, de forma que podamos usarlo en nuestro programa.
Una vez completados estos pasos podemos continuar y usar el objeto a travs de llamadas a sus mtodos. Estos usan la notacin estndar del punto, con el nombre del objeto seguido del nombre del mtodo y cualesquiera parmetros que sean necesarios.
Pgina 12 de 28
Creado el 16/06/09
Varias opciones funcionarn. Podemos tener la llamada a AddItem en el manejador de eventos OnClick del btnAddBox cuatro veces:
procedure TfrmCount.btnBoxClick(Sender: TObject); begin // una pobre aproximacin ItemCount.AddItem; ItemCount.AddItem; ItemCount.AddItem; ItemCount.AddItem; end;
Esto, o algo parecido a un bucle For, no es una buena idea ya que perdemos la separacin de conceptos -la interfaz de usuario debe conocer que una caja contiene cuatro elementos-. Otra posibilidad es aadir un mtodo AddBox a la clase TItem. Esta es una mucho mejor aproximacin. Una tercera alternativa es crear una subclase de TItem conteniendo un mtodo AddBox. Ambas alternativas mantienen la separacin de capacidades, mientras que el tercer mtodo adems clarifica la distincin entre Elemento y Caja.
Pgina 13 de 28
Creado el 16/06/09
13 public 14 procedure AddBox; // Public access method 15 end; // end TItemBox = class(TItem) 16 implementation 17 18 19 20 21 { TItem } procedure TItem.AddItem; begin Inc(FCount); end; // end procedure TItem.AddItem
22 function TItem.GetCount: integer; 23 begin 24 Result := FCount; 25 end; // end function Titem.GetCount 26 procedure TItem.ZeroCount; 27 begin 28 FCount := 0; 29 end; // end procedure Titem.ZeroCount 30 31 32 33 34 35 36 { TItemBox } procedure TItemBox.AddBox; const NoInBox = 4; begin Inc (FCount, NoInBox); end; // end procedure TitemBox.AddBox
Aqu, en adicin a la definicin de TItem (lneas 411) que tenamos previamente, definimos una nueva clase llamada TItemBox (lneas 1215). Mientras que TItem est derivado de TObject (lnea 4), TItemBox est derivado de TItem (lnea 12). TItemBox es una subclase de TItem y por ello hereda todos los datos y mtodos que tiene TItem. Este es un simple ejemplo de reutilizacin en OO -toda la funcionalidad del TItem est disponible en el TItemBox sin reescribir ni una sla lnea de cdigo-. TitemBox tambin tiene su propio mtodo, el procedimiento AddGoal declarado en la lnea 14. En la seccin de implementacin (lneas 1636) los mtodos del TItem AddItems, GetCount y permanecen como antes. Las lneas 3136 definen el mtodo del TitemBox AddBox, el cual incrementa el valor de FCount (definido en la superclase TItem).
ZeroCount
Pgina 14 de 28
Creado el 16/06/09
Ya que TItemBox usa el campo de datos FCount definido en el TItem no necesitamos reimplementar el para TItemBox sino simplemente basarnos en la definicin heredada. Ya que la herencia trabaja slo en una direccin TItemBox tiene los datos y mtodos de TItem como parte de su estructura, pero TItem no tiene el mtodo AddBox que est declarado en TitemBox.
ZeroCount
Aunque los mtodos tanto para TItem como para TItemBox aparecen en la seccin de implementacin de sta unit, Delphi los distingue mediante sus cabeceras, ya que la clase aparece antes del punto en los nombres. Advierta que aunque FCount est declarado como privado para TItems, TBoxItems puede acceder a l. Esto es debido a que los especificadores de visibilidad private, protected y public en Delphi se aplican a la unit en lugar de a la clase. Desde Delphi 8, Delphi tambin tiene strictly private y strictly protected que se aplican a la clase est o no empaquetada en la misma unit como otra clase. El diagrama de clase ahora incluye la nueva subclase TItemBox (Figura 9).
Pgina 15 de 28
Creado el 16/06/09
Aunque ahora declaramos ItemCount como un TItemBox y no como un TItem, todos los manejadores de evento existentes permanecen igual. Creamos un nuevo manejador de evento para usar la facilidad aadida y esto usa mtodos disponibles slo para un TItemBox y no para un TItem.
Pgina 16 de 28
Creado el 16/06/09
Figura 10: Usando el Explorador de Cdigo para navegar a travs del cdigo
El Navegador de Proyecto
Ni el Explorador de Cdigo (Figura 10) ni el Visor de Objetos (Figura 7) muestran la jerarqua de herencia de clases, as que echaremos un breve vistazo al Navegador de Proyecto de Delphi, disponible en View | Browser en versiones de Delphi inferiores o iguales a Delphi 7 (el proyecto cargado debe haberse ejecutado en la sesin actual antes de que la opcin del navegador se vuelva disponible). El Navegador de Proyecto muestra las clases que hemos creado en ste proyecto junto con su estructura de herencia (Figura 11, panel izquierdo). Vemos TObject como la clase principal con TItem derivado desde TObject y TItemBox derivado a su vez desde TItem. Tambin vemos que el formulario que creamos, TfrmCount, est derivado desde TForm, el cual a su vez est derivado desde una serie de otros objetos todos nacidos de TObject (vimos la herencia VCL de Delphi en el captulo 2). Si seleccionamos TItemBox en el panel izquierdo, en el panel derecho vemos que TItemBox tiene un nico miembro pblico, el mtodo AddBox. ste ha heredado el campo de datos privado, FCount, y un lote de mtodos pblicos heredados desde TItem y TObject incluyendo al constructor Create cerca del final (bottom) del panel. El navegador tiene muchas ms capacidades. Para ms informacin presione <F1> mientras el navegador es la ventana activa.
Pgina 17 de 28
Creado el 16/06/09
Figura 11: El navegador de proyecto de Delphi mostrando tanto las clases estndar de Delphi como las definidas localmente
Sera mejor, a pesar de tener un mtodo en la superclase TItem, agregar un nmero determinado de puntos en el campo Fcount. Esto lleva al concepto de mtodos sobrecargados: aadir un mtodo AddItem protegido con el nmero a aadir como parmetro (lnea 8 debajo). Esto es en adicin al mtodo AddItem que ya tenemos, aunque ste no tiene parmetros (lnea 10 debajo). As que ahora tenemos dos mtodos con el mismo nombre. Cmo distingue Delphi entre ellos? Si llamamos a AddItem sin parmetros, como en el ejemplo 3.2, paso 2, lnea 31, Delphi invocar al mtodo con el nombre requerido y sin parmetros (p.ej., lneas 2023 debajo). Si llamamos a AddItem con un nico parmetro entero, como en la lnea 41 de debajo, Delphi invocar a la implementacin del mtodo con el mismo nmero de parmetros (p.ej., lneas 2427 debajo). Para informar al compilador que estamos sobrecargando ste nombre de mtodo, debemos aadir la palabra clave overload en cada declaracin de AddItem (lneas 8 y 10 debajo). Los nombres de mtodo sobrecargados deben tener siempre diferentes listas de parmetros en trminos de tipos o nmero de parmetros, para que el compilador pueda distinguirlos cuando sean llamados.
Pgina 18 de 28
Creado el 16/06/09
Podemos evitar la sobrecarga si usamos distintos nombres, p.ej., AddItem y AddItems(). Sin embargo, en casos en los que deseamos realizar la misma operacin pero con distinto nmero o tipo de parmetros, p.ej., enteros o dobles, es particularmente til tener la capacidad de sobrecargar el mismo nombre.
1 unit ItemU; 2 interface 3 type 4 TItem = class(TObject) 5 private 6 FCount: integer; 7 protected 8 procedure AddItem (aNoToAdd: integer); overload; 9 public 10 procedure AddItem; overload; 11 function GetCount: integer; 12 procedure ZeroCount; 13 end; // end TItem = class(TObject) 14 TItemBox = class(TItem) 15 public 16 procedure AddBox; 17 end; // end TItemBox = class(TItem) 18 implementation 19 20 21 22 23 { TItem } procedure TItem.AddItem; begin Inc(FCount); end; // end procedure TItem.AddItem
24 procedure TItem.AddItem(aNoToAdd: integer); 25 begin 26 Inc(FCount, aNoToAdd); 27 end; // procedure TItem.AddItem(aNoToAdd: integer); 28 function TItem.GetCount: integer; 29 begin 30 Result := FCount; 31 end; // end function Titem.GetCount 32 procedure TItem.ZeroCount; 33 begin 34 FCount := 0; 35 end; // end procedure Titem.ZeroCount 36 37 38 39 40 41 42 { TItemBox } procedure TItemBox.AddBox; const NoInBox = 4; begin AddItem(NoInBox); end; // end procedure TitemBox.AddBox
Debido a que estos cambios estn encapsulados dentro de las clases TItem y TitemBox. La interfaz de usuario del ejemplo 3.3 no necesita alteracin para interactuar con sta unit, como ver cuando lo ejecute.
Pgina 19 de 28
Creado el 16/06/09
Advierta que AddItem sin parmetros es declarado como pblico (lnea 10 anterior) y as puede ser llamado directamente por la clase de interfaz de usuario (p.ej., ejemplo 3.2, paso 2, lnea 31). Sin embargo, AddItem con el parmetro es declarado como protegido y as no puede ser llamado por el objeto de interfaz de usuario. Para ver esto, cambie al ejemplo 3.3, paso 2, lnea 49 para llamar a AddItem:
47 procedure TfrmCount.btnBoxClick(Sender: TObject); 48 begin 49 ItemCount.AddItem(4); // out of scope 50 end; // end procedure TfrmCount.btnBoxClick
Esto provoca un error de compilador. En Delphi los especificadores de visibilidad private y protected se aplican a la unit. As en ste caso hemos reemplazado protected por private y el programa todava funcionar. Sin embargo, si stas dos clases son definidas en units separadas el procedimiento sobrecargado AddItem (aNoToAdd) no estar accesible con visibilidad privada y por tanto debe ser protegido (no queremos hacerlo pblico porque queremos que su visibilidad quede restringida a TItem y sus descendientes).
Los nombres de objeto son actualmente referencias al bloque de memoria que almacena del dato nico para ese objeto. La declaracin del nombre reserva memoria en la pila para mantener la referencia al objeto, pero no para el objeto mismo (Figura 13).
Pgina 20 de 28
Creado el 16/06/09
Cuando invocamos al objeto constructor, esto reserva memoria para almacenar el objeto mismo, inicializa los valores y proporciona el enlace a la clase (Figura 14).
Creado el 16/06/09
El ejemplo 3.3 introduce la herencia. Esto significa que necesitamos crear una tabla adicional para los mtodos de subclase y luego enlazar a ellos nuestro objeto, tal como se ilustra en la Figura 16 (advierta que aunque no tenemos un objeto TItem en el ejemplo 3.3, an tenemos una tabla de la clase TItem).
Pgina 22 de 28
Creado el 16/06/09
Si creamos varios objetos necesitamos repetir la tabla de objeto pero no la tabla de clase. As que si tenemos otro objeto, MoreItems, de tipo TItemBox, obtendremos la Figura 17.
Este otro objeto MoreItems podra ser en su lugar del tipo TItem (Figura 18) en cuyo caso ste tiene acceso a los mtodos TItem pero no a los mtodos TItemBox:
En la Figura 18 ambos objetos tienen la misma estructura de datos. Esto es as porque la subclase en el ejemplo 3.3 aade mtodos adicionales pero no campos de datos adicionales a la superclase. Aadiremos campos de datos adicionales a una subclase ms adelante en ste mdulo. En suma, como se ilustra en la Figura 17, cada referencia a objeto se refiere a un bloque de datos especfico para ese objeto. As diferentes objetos de la misma clase tienen cada uno sus propios datos. Sin embargo, distintos objetos de la misma clase cada una de ellas transporta una referencia a las mismas definiciones de mtodos compartidas. Cada subclase define sus propios mtodos y lleva un puntero a sus mtodos de superclase para la definicin de los mtodos heredados, y as sucesivamente hasta la jerarqua.
Aprendiendo OOP con Delphi Pgina 23 de 28
Creado el 16/06/09
Aunque estn muy simplificados, estos diagramas son bastante complicados. Podran ser difciles de utilizar en una programacin normal. Afortunadamente, generalmente no vamos a necesitar pensar en estos trminos y podremos usar la abstraccin proporcionada por los diagramas (p.ej., la Figura 9).
Mantener los objetos de interfaz de usuario y de aplicacin separados: separacin de capacidades Mimetizar objetos de interfaz de usuario RAD en clases de aplicacin definidas por el usuario Pasos para usar un objeto definido por el programador:
Herencia: derivar una nueva clase desde una existente Empaquetar una clase en una unit El Explorador de Cdigo y el Navegador de Proyecto Diseo de objetos en memoria
Puntos principales: 1. Definicin y uso de una clase y una subclase 2. Semntica de referencia y diseo de objeto en memoria 3. Separacin de capacidades: interfaz de usuario y clases de dominio
Objetos como entidades independientes: clase definida por el programador Objetos como entidades derivadas: herencia definida por el programador Objetos como entidades interactuantes: mtodos de acceso, visibilidad, sobrecarga
Pgina 24 de 28
Creado el 16/06/09
Problemas
Problema 3.1. Estudio de Captulo 3
Identifique los apropiados ejemplos o secciones del captulo para ilustrar cada comentario hecho en el sumario anterior.
En la escritura de ste programa colocamos las definiciones de TItem y TItemBox en units separadas.
Pgina 25 de 28
Creado el 16/06/09
Hay varias formas de estructurar las clases de la aplicacin. Una podra ser, por ejemplo, usar una estructura fuertemente jerrquica (Figura 22) o una estructura relativamente dbil (Figura 23) .
1. Contraste estas dos aproximaciones, describiendo las ventajas y desventajas de cada una. 2. Implemente la versin de jerarqua profunda. 3. Implemente la versin de jerarqua dbil. Tras haber realizado estas dos implementaciones, puede encontrar que desee aadir o modificar sus comentarios en la parte 1 de sta pregunta.
Pgina 26 de 28
Creado el 16/06/09
una seccin de entrada de texto, la cual muestra asteriscos cuando se escribe texto, un botn para enviar la clave y un rea de respuesta.
Si la contrasea es correcta, la respuesta es Proceda (Figura 24). Si la contrasea es incorrecta, la respuesta es Reporte a escritorio principal.
De acuerdo con el Principio de Separacin de Capacidades, la interfaz de usuario estar separada de la clase aplicacin (TVerify) para determinar si la contrasea es correcta o no (Figura 26). La interfaz de usuario enva un mensaje al objeto aplicacin dando una clave (la cual representa la contrasea correcta) y la palabra introducida por el usuario. El objeto de verificacin de la contrasea entonces retorna True o False. Actualmente el algoritmo de verificacin es muy simple: el usuario debe introducir el reverso de los que es la clave. As que si la clave es Mary, el usuario debe introducir yraM como contrasea. La Figura 25 muestra los componentes en la interfaz de usario, y la Figura 26 presenta un diagrama de clase. Escriba ste programa. La clave debera ser Mary, con units separadas para TfrmPassword (Figura 24) y para TVerify.
Pgina 27 de 28
Creado el 16/06/09
El sistema ahora puede ser extendido. La primera puerta de acceso permanece como antes (Figura 24). una segunda puerta con una segunda, separada interfaz de usuario como la interfaz de la primera puerta es aadida. Esta segunda puerta de acceso tiene dos niveles de chequeo de contrasea. Si el usuario introduce la primera clave correctamente en la segunda puerta, el sistema entonces displaya un form adicional solicitando al usuario una segunda, distinta contrasea (Figura 27). Slo si sta tambin es correcta se permite al usuario proceder. Si la primera clave es incorrecta, o si la primera es correcta pero la segunda no, el usuario debe recibir el mensaje Reporte a Escritorio Principal. El diagrama de clase se muestra en la Figura 28. Escriba ste programa. La primera clave es Mary y la segunda es George. El programa actualizado ahora tiene cuatro units separadas, una para la primera puerta (TfrmPassword, Figura 24), para la segunda (TfrmPassword1 derivado desde TfrmPassword), para el formulario adicional TfrmPassword2 (Figura 27) y para TVerify.
Figura 27: La pantalla de entrada adicional (para la segunda clave) Figura 28: Diagrama de clase para control de clave de segundo nivel
Histrico de Cambios
1. Problema 3.2 se refiere a ejemplo 3.3. 2. Nueva figura insertada (Figura 21). La figuras subsecuentes se incrementan de nmero apropiadamente. 3. Pgina 9, prrafo final: Advierta que hemos cambiado el nombre FCount a FCount Cambiado a ...Count to FCount.
Pgina 28 de 28