Vous êtes sur la page 1sur 12

Creado el 17/06/09

5 El Parmetro
Sender y la Sustitucin

Conceptos Principales

Sustitucin El parmetro Remitente (Sender) Conversin (Typecasting) Operadores de clase e instancia RTTI: tipo de informacin en tiempo de ejecucin

Aprendiendo OOP con Delphi

Pgina 1 de 12

Creado el 17/06/09

Introduccin
Una consecuencia importante de una jerarqua de herencias es el concepto de sustitucin, donde una instancia de un objeto subclase puede sustituir a una variable declarada como cualquiera de sus supertipos. En ste breve captulo introduciremos el concepto de sustitucin investigando el parmetro Sender, que es parte de la lista de parmetros de cada manejador de eventos. Diferentes eventos tienen diferentes listas de parmetros pero el primero en la lista para todos los eventos es Sender: TObject, el cual identifica al componente que ha iniciado el evento. La identificacin del evento iniciador es una idea til, pero no completamente sencilla. La dificultad es que cada parmetro debe tener un tipo, y por tanto Sender debe ser declarado como un tipo especfico. Pero Qu tipo debera ser Sender? Cada componente es diferente. Como veremos en el ejemplo que sigue, Delphi usa la jerarqua de la herencia para resolver ste problema.

Ejemplo 5.1: el Parmetro Sender en Manejadores de Eventos


El ejemplo que usaremos para ilustrar el parmetro Sender tiene seis SpeedButtons, dos paneles, una etiqueta y un formulario (figuras 2 y 1). El objetivo de ste programa es displayar un mensaje apropiado en respuesta a un click sobre cualquiera de esos componentes. Una forma de hacer esto es escribir diez manejadores de evento, uno por cada componente, y cada uno displayando el mensaje requerido. Pero esto requerira un esfuerzo absurdo y, por supuesto, el cdigo repetido introduce ms oportunidades de futuros errores. Lo mejor sera escribir slo un procedimiento, para enrutar todos los eventos OnClick a travs de ste cdigo, y luego usar el parmetro Sender para generar un mensaje identificando el componente que ha sido pulsado.

Figura 1: Estructura de la interfaz de usuario

Figura 2: Interfaz de usuario para el ejemplo 5.1.

Ejemplo 5.1 paso 1: El manejador de Eventos OnClick, Versin 1


Comenzamos con un cdigo simple y entonces lo convertiremos en el programa completo. Cree un manejador de evento OnClick para spdOne para displayar el valor del Caption de spdOne y pruebe la primera versin del programa.
28 procedure TfrmSender.spdOneClick(Sender: TObject); 29 begin 30 pnlReport.Caption := 'You clicked on ' + spdOne.Name; 31 end; // procedure TfrmSender.spdOneClick

Aprendiendo OOP con Delphi

Pgina 2 de 12

Creado el 17/06/09

Ejemplo 5.1 paso 2: Usando el Parmetro Sender


Ahora modificaremos ste manejador de evento para usar el parmetro Sender. Una posibilidad es cambiar spdOne en el estamento de asignacin a Sender (lnea 30 debajo):
28 procedure TfrmSender.spdOneClick(Sender: TObject); 29 begin 30 pnlReport.Caption := 'You clicked on ' + Sender.Name; 31 end; // procedure TfrmSender.spdOneClick

Si intenta esto comprobar que no funciona. El compilador retorna un error Undeclared identifier. Por qu? En la lista de parmetros en la cabecera de ste manejador de evento OnClick, y en todos los dems manejadores de eventos, el tipo para Sender es definido como TObject, la clase raz de la jerarqua, y no como TSpeedButton (lnea 28). La clase TObject no tiene una propiedad Name, y por tanto, cuando el compilador busca Sender.Name en la lnea 30, sta genera un error. Debido a que Sender es un parmetro completamente general, ste es declarado como tipo TObject. Sin embargo, en ste caso sabemos que Sender es un TSpeedButton y por tanto podemos instruir a Delphi para que trate a Sender como un TSpeedButton -en otras palabras, podemos convertir (typecast) Sender en un TSpeedButton. Hay varias formas para hacer esto. Aqu usamos el operador de clase as como sigue (lnea 31):
28 procedure TfrmSender.spdOneClick(Sender: TObject); 29 begin 30 pnlReport.Caption := 'You clicked on ' + 31 (Sender as TSpeedButton).Name; 32 end; // procedure TfrmSender.spdOneClick

Este programa compila y funciona como la primera versin, as que ahora estamos usando el parmetro
Sender correctamente.

Hemos visto al operador de conversin as en el ejemplo 2.1, paso 2, lnea 17, y aqu lo miraremos ms detenidamente. Esta conversin informa a Delphi que aunque Sender es declarado en la lista de parmetros como un TObject podra ser cualquiera de un nmero de distintas clases, y en ste particular caso Delphi debe tratarlo como un TSpeedButton (lnea 31 arriba).

Ejemplo 5.1 paso 3: Enlazando con los otros SpeedButtons


Ninguno de los dems botones displaya ningn mensaje todava y ahora debemos prestar atencin en enlazarlos. Cierre el programa, seleccione spdOne y entonces seleccione la pestaa de Eventos en el Inspector de Objetos. Para hacer las cosas menos confusas, cambie el nombre del manejador OnClick de spdOneClick a GeneralClick. Delphi ahora automticamente har ste cambio en el cdigo de programa (no cambie el programa directamente). Ahora enlace los otros SpeedButtons a ste manejador de evento. Seleccionando cada SpeedButton, sobre la pestaa Eventos del Inspector de Objetos seleccinoe GeneralClick desde el combobox (Figura 3). Esto enruta cada evento de SpeedButton al nico manejador de evento General Click. Almacene sta versin del programa y ejectelo. Compruebe que el texto en el panel da el correcto nombre de manejador de eventos para cada uno de los SpeedButtons.

Aprendiendo OOP con Delphi

Pgina 3 de 12

Creado el 17/06/09

Figura 3: Enlazando los distintos componentes al mismo manejador de eventos

Ejemplo 5.1 paso 4: Testeando el Parmetro Sender


Qu pasa si un manejador de eventos recibe eventos desde ms de un tipo de componente? Por ejemplo, Qu pasa si enlazamos el manejador de eventos GeneralClick para responder a los paneles del formulario as como a los clicks de los SpeedButtons? Cmo decide entonces si convertir el parmetro Sender como un TSpeedButton, como un TPanel o como un TForm? Delphi tiene un operador especial, el operador de clase is, que determina a qu clase de componente de refiere Sender (lneas 30, 33 y 35).
28 procedure TfrmSender.GeneralClick(Sender: TObject); 29 begin 30 if Sender is TSpeedButton then 31 pnlReport.Caption := 'You clicked on ' + 32 (Sender as TSpeedButton).Name 33 else if Sender is TForm then 34 pnlReport.Caption := 'You clicked on ' + (Sender as TForm).Name 35 else if Sender is TPanel then 36 pnlReport.Caption := 'You clicked on ' +(Sender as TPanel).Name; 37 end; // fin TfrmSender.GeneralClick

Usando la pestaa de Eventos del Inspector de Objetos, enlace los eventos OnClick de pnlControl, y frmSender al manejador de eventos GeneralClick (utilizamos una condicin incluso en la ltima clusula else para asegurarnos que responder slo a TPanels. Cualquier cosa excepto un TSpeedButton, un TForm o un TPanel es ignorado). Ejecute y testee sta versin de programa y compruebe que responde correctamente a los clicks en los paneles y el formulario as como en los SpeedButtons. No responde a un click sobre lblInheritance aunque lo hayamos enlazado a GeneralClick como su manejador de eventos, ya que GeneralClick no tiene condicin If para testear un TLabel.
pnlReport, lblInheritance

Ejemplo 5.1 paso 5: Trabajando a travs de una Superclase


El programa del paso 4 anterior es muy interesante. Nos muestra que una variable de un tipo, en ste caso el parmetro Sender de tipo TObject, puede representar a cualquiera de sus descendientes, en ste caso TSpeedButton, TForm o TPanel. Podemos tomar ste concepto para dar un paso ms en el proceso de reducir el nmero de estamentos If del paso 4 de tres a uno. Cuando discutamos el concepto de jerarqua de clases en el captulo 2, mencionamos que TComponent introduce la propiedad Name.

Aprendiendo OOP con Delphi

Pgina 4 de 12

Creado el 17/06/09

TSpeedButton, TForm y TPanel son todos descendientes de TComponent, eso es por lo que ellos tienen todos la propiedad Name, y el TComponent es a su vez un descendiente de TObject. Podemos aprovechar esto para nuestro programa trabajando a nivel de TComponent en vez de clases individuales (lneas 3032

debajo):
28 procedure TfrmSender.GeneralClick(Sender: TObject); 29 begin 30 if Sender is TComponent then 31 pnlReport.Caption := 'You clicked on ' + 32 (Sender as TComponent).Name 33 end; // fin TfrmSender.GeneralClick

Almacene y ejecute sta versin de programa. Da los mismos mensajes que la del paso 4 si hacemos click sobre un SpeedButton, un panel o el formulario, y ahora tambin responde a un click sobre lblInheritance. Aqu obtenemos un nuevo beneficio desde la herencia. Debido a que TSpeedButton, TPanel, TLabel y son todos descendientes de TComponent, podemos probar que sean un TComponent (lnea 30) o desciendan de un TComponent (lnea 32) en lugar de probar y convertir cada tipo de componente por separado como en el paso 4.
TForm

Donde estamos usando una propiedad (o mtodo), en la que las subclases heredan desde una superclase, podemos trabajar con la superclase en vez de con cada subclase individualmente. Esto nos ahorra programacin, ya que no tenemos que atender separadamente para cada subclase de la clase y as podemos evitar situaciones como la del paso 4, donde tenemos un estamento If separado para cada subclase. Tambin significa que si a futuro introducimos una subclase ms lejana no tenemos que buscar a travs de todo el programa para atenderla especficamente a ella. Lo que vemos en ste paso es importante. Sender, de tipo TObject, es lanzado como descendiente de un TComponent, el cual a su vez es un ancestro de varios otros objetos usados aqu (TspeedButton, TPanel y TForm). Como todos estos objetos heredan la propiedad Name de su ancestro TComponent, podemos lanzar Sender como a su ancestro TComponent. Esto nos permite trabajar con ellos como grupo en lugar de con cada uno individualmente. Aunque estamos trabajando con un TComponent en las lneas 30 y 32, cualquiera de sus descendientes (aqu TSpeedButton, TPanel o TForm) puede sustituirlo, comportndose como si fueran un TComponent. Este es un ejemplo de sustitucin.

Ejemplo 5.1 paso 6: Una alternativa al Operador de Conversin


El paso 5 es el foco principal de se ejemplo. Pero hay dos cosas a hacer antes de completar el ejemplo. La primera es que el operador as es parte de Delphi Pascal pero no los es del Pascal original. as tiene la ventaja de hacer chequeo estricto de errores y lanzar una excepcin si uno intenta convertir de forma incorrecta. La conversin original de Pascal era a travs de parntesis, y Delphi tambin la reconoce. As que podemos cambiar la lena 32 del programa anterior por:
28 procedure TfrmSender.GeneralClick(Sender: TObject); 29 begin 30 if Sender is TComponent then 31 pnlReport.Caption := 'You clicked on ' + 32 TComponent(Sender).Name 33 end; // end TfrmSender.GeneralClick

La notacin mediante parntesis se parece un poco a una llamada a subrutina y no realizar ningn chequeo de errores. An as, aunque es algo menos eficiente, muchos programadores la prefieren como operador de conversin ya que es ms robusta y fcil de leer.

Aprendiendo OOP con Delphi

Pgina 5 de 12

Creado el 17/06/09

Ejeplo 5.1 paso 7: El Operador de Igualdad


Hemos usado el parmetro Sender con los operadores de clase is y as. El primero, is, determina la clase de un objeto y frecuentemente forma la parte condicional de un estamento If. As en el paso 6 anterior usamos is para determinar a qu clase representa Sender. Sin embargo, si queremos saber a qu componente especfico u objeto representa Sender podemos usar el operador de igualdad =. Por ejemplo, asumamos que el color del formulario debera ser aqua si spdFive es pulsado y green si cualquier otro componente es pulsado. Podemos hacer esto como sigue (lneas 3336):
28 procedure TfrmSender.GeneralClick(Sender: TObject); 29 begin 30 if Sender is TComponent then 31 pnlReport.Caption := 'You clicked on ' + 32 (Sender as Tcomponent).Name; 33 if Sender = spdFive then 34 Color := clAqua 35 else 36 Color := clGreen; 37 end; // fin TfrmSender.GeneralClick

Ejemplo 5.1. Sumario


El punto crucial de ste ejemplo es introducir el concepto de sustitucin. El parmetro Sender, el cual es parte de cada evento, le dice al manejador de eventos qu componente inici el evento. Muchos distintos componentes pueden iniciar eventos y el parmetro Sender es de tipo TObject, el tipo ms general de objeto que hay en Delphi. Los operadores is y = nos permiten comprobar si Sender representa una clase particular o un objeto particular. Para usar Sender en un programa necesitamos convertirlo a un valor apropiado y podemos usarlo como (con chequeo de errores) o () (sin chequeo) para la conversin. En ste ejemplo vimos que una propiedad que est disponible para un objeto TComponent est tambin disponible para un TSpeedButton, TForm y TPanel porque todos ellos descienden de TComponent. Debido a la jerarqua de la herencia, un subtipo hereda toda la funcionalidad del supertipo como punto de inicio, y por tanto un subtipo puede tomar el rol de cualquiera de sus ancestros. Sin embargo, un ancestro, el cual es tpicamente ms restrictivo que un subtipo, no puede tomar todos los roles del subtipo, e intentar sustituir a un ancestro por un subtipo dar lugar a un error de compilacin.

Vista en Diagrama de la Sustitucin


Para consolidar el concepto de sustitucin, podra ser til preguntarnos cmo es posible para un subtipo sustituirlo por un ancestro, y responderemos a esto mirando cmo un objeto es (conceptualmente) representado en memoria. Asumamos que hay cuatro clases, Con la clase A como el ancestro de las clases B y C, y una clase D que es una subclase de la clase B (Figura 4).

Aprendiendo OOP con Delphi

Pgina 6 de 12

Creado el 17/06/09

Figura 4: Una jerarqua simple

El diagrama de clase muestra en la clase B slo los campos de datos adicionales declarados en la clase B, con la flecha de generalizacin desde la clase B a la clase A indicando que la clase B tambin tiene campos de datos declarados en la clase A. Sin embargo, una instancia de la clase B debe tener memoria reservada para los Campos de Datos A y los Campos de Datos B (y similarmente para las otras clases). Digamos que tenemos una instancia de la clase A, dos instancias de clase Class B, una instancia de clase C y una instancia de clase D. Podemos representar esto como sigue:

Figura 5: Esquema de estructura de un objeto

Cuando una instancia de clase B, C o D sustituye para una instancia de clase A, la estructura de subclase adicional (Campos de Datos B, C y/o D) son ignorados. Esto es posible porque el solapamiento en la estructura entre una superclase y todos sus descendientes. Sin embargo, la clase B no puede sustituir a la clase C o viceversa por las diferencias en sus estructuras. Una instancia de la clase D puede sustituir instancias tanto de la clase B como de la clase A (pero no para la clase C). Nos hemos referido slo a los campos de datos de los objetos hasta ahora. Un concepto similar pero ms sofisticado se aplica a los mtodos y, donde tenemos enlaces en tiempo de ejecucin, hace posible el polimorfismo. El polimorfismo es un concepto intrigante, y dedicaremos el prximo captulo a l.

Aprendiendo OOP con Delphi

Pgina 7 de 12

Creado el 17/06/09

Ejemplo 5.2. Informacin de Tipo en Tiempo de Ejecucin (RTTI)


Como hemos visto desde los operadores is y as que usamos en el ejemplo 5.1, Delphi puede determinar aspectos de la estructura de clase de un objeto en tiempo de ejecucin. En Win32, cada objeto es asociado con su informacin de tipo en tiempo de ejecucin, usualmente abreviada como RTTI. Hay en RTTI ms cosas que slo is y as, aunque puede que sean los ms comnmente usados. Por ejemplo, el mtodo ClassName retorna un string, el mtodo InheritsFrom retorna un Boolean, y tanto ClassType como ClassParent retornan una variable TClass. Puede modificar el programa anterior para ilustrar estos mtodos RTTI:
28 procedure TfrmSender.GeneralClick(Sender: TObject); 29 begin 30 lblInheritance.Caption := 'Class Name: ' + Sender.ClassName; 31 pnlReport.Caption := 'TCustomControl descendant: ' + 32 BoolToStr(Sender.InheritsFrom(TCustomControl), True); 33 end; // procedure TfrmSender.GeneralClick

Figura 6: Usando algunos mtodos RTTI

Algunos Comentarios Finales


El concepto de sustitucin es que cuando declaramos una variable como un tipo particular, esa variable puede referirse a un objeto del tipo declarado o de cualquiera de sus tipos descendientes. Sintcticamente, esto significa que podemos usar un objeto de una subclase siempre que un objeto de cualquiera de sus ancestros es especificado (si sobreescribimos nuestros mtodos de superclase indiscriminadamente en nuestras subclases, o si no mantenemos una generalizacin significativa en nuestra jerarqua, podemos daar la semntica de la sustitucin. Pero ste es el asunto del prximo captulo, y slo se produce por una mala prctica de programacin). Existe una relacin entre la generalizacin (mencionada en el captulo 2) y la sustitucin, y vale la pena retornar brevemente a la generalizacin. La generalizacin es el proceso de identificacin de los elementos comunes que existen entre un conjunto de clases y luego mover estos elementos comunes a una superclase (podemos crear una superclase especialmente para la generalizacin). Como los elementos comunes de un grupo de subclases han sido movidos a una superclase, tiene sentido que cualquiera de las subclases puedan sustituir a la superclase (aunque no al revs). Esta posibilidad de sustitucin es una razn importante para introducir la generalizacin en la estructura de clase de la aplicacin siempre que sea posible. En los componentes VCL, los desarrolladores Delphi ya haban desarrollado la jerarqua de clase y producido varios niveles de generalizacin. Es por esto por lo que podemos usar la sustitucin como hicimos en ste captulo, con un SpeedButton, Panel, Label o Form sustitudos por un TComponent. Cuando empezemos a disear nuestras propias clases valdr la pena mantener los conceptos de generalizacin y la sustitucin en mente.

Aprendiendo OOP con Delphi

Pgina 8 de 12

Creado el 17/06/09

Este es un breve captulo que simplemente ilustra el concepto de sustitucin para preparar el camino al polimorfismo. En el prximo captulo exploraremos la generalizacin y la sustitucin, sta vez con clases escritas por el programador, y desarrollaremos el concepto para incluir polimorfismo. El polimorfismo es fundamental para varios de los patrones que investigaremos ms tarde y es una de las principales razones para el crecimiento y desarrollo de la programacin orientada a objetos.

Sumario del Captulo


Puntos principales: 1. Sustitucin. 2. El parmetro Sender. 3. Conversin (Typecasting). 4. Operadores de clase e instancia. 5. RTTI: informacin de tipo en tiempo de ejecucin. 6. Objetos como entidades derivadas: Sustitucin.

Aprendiendo OOP con Delphi

Pgina 9 de 12

Creado el 17/06/09

Problemas
Problema 5.1. Estudio de Captulo 5
Identifique los ejemplos o secciones adecuados del captulo para ilustrar cada comentario hecho en el sumario anterior.

Problema 5.2. RTTI


Considere el siguiente programa:

Figura 7: Pantalla de inicio para el problema 6.1

Figura 8: Objetos para la interfaz de usuario

Los manejadores de eventos para todos los objetos de la interfaz de usuario son enrutados a travs del siguiente manejador de eventos:
23 procedure TfrmSender.GeneralClick(Sender: TObject); 24 var 25 Class1, Class2: TClass; 26 ClassStr: string; 27 begin 28 Class1 := Sender.ClassType;

Aprendiendo OOP con Delphi

Pgina 10 de 12

Creado el 17/06/09

29 30 31 32 33 34 35 36 37

Class2 := Class1.ClassParent; while Class2 <> nil do begin ClassStr := Class1.ClassName + ' derived from ' + Class2.ClassName; lstClasses.Items.Add(ClassStr); Class1 := Class1.ClassParent; Class2 := Class1.ClassParent; end;

38 lstClasses.Items.Add(''); 39 end; // procedure TfrmSender.GeneralClick 40 end. // fin SenderU

a) Explique cmo ste manejador de eventos funciona y qu hace, refirindose a los principios cubiertos en el captulo 5. b) Dibuje el diagrama de clase UML para todas las clases en ste programa sobre las bases de la operatoria del programa.

Problema 5.3: Control centralizado de varias luces de trfico


Estas preguntas desarrollan los conceptos y el programa cubiertos en las propiedades anteriores en base al problema del semforo de trfico del captulo 4. Para ste problema, cree un panel de control maestro (figuras 9 y 10) que controle la visualizacin de tres semforos distintos.

Figura 9: El panel de control de luces con los tres semforos distintos

Aprendiendo OOP con Delphi

Pgina 11 de 12

Creado el 17/06/09

Figura 10: El panel de control de luces de trfico

Ese panel de control proporciona control independiente de tres semforos distintos. Cada luz de trfico puede ser configurada automtica o manualmente va un CheckBox. Cuando el CheckBox es chequeado la luz de semforo asociada funciona automticamente (4 ciclos rojo, 3 ciclos verde, 1 ciclo amarillo). Cuando el CheckBox est deseleccionado, el botn Step es activado. Cada click en el botn Step mueve la luz de trfico asociada, independientemente de las otras dos, al siguiente color en la secuencia. El tercer control para cada luz, un SpinEdit, da la longitud de ciclo en dcimas de segundo. As que si SpinEdit se establece a 8, la luz que controla pasa 3.2 segundos en rojo, 2.4 en verde y 0.8 en amarillo. Cualquier cambio de valor del SpinEdit toma efecto desde el siguiente cambio de color. Codifique ste programa, incorporando los principios OO cubiertos hasta ahora como considere apropiado. Incluya documentacin UML simple. Hay varias formas de atacar a ste problema. Los programadores menos experimentados podran encontrar tiles los siguientes pasos: 1. Escriba una simple descripcin de algoritmo de bajo nivel para cada uno de los siguientes casos: a) Active una luz de trfico para operacin automtica chequeando un CheckBox. b) Desactive el paso automtico y hgalo manualmente a travs de la secuencia pulsando sobre un botn. c) Prepare un CheckBox para operacin automtica y luego cambie el ciclo temporal. 2. Desde stas descripciones, decida qu clases son necesarias y cmo deberan ser relacionadas. 3. En base a las clases del diagrama de clases, dibuje un diagrama de secuencia para cada una de las clases anteriores. 4. Implemente cada caso. Probablemente es ms sencillo no resolver todo el problema de una sla vez. En lugar de eso, puede decidir comenzar slo con el controlador y con una sla luz de trfico y concentrarse en cambiar la operacin automtica de dicha luz de on a off correctamente. Entonces tendra la posibilidad de elegir. Podra tener las tres luces controladas independientemente. Entonces podra obtener control manual trabajando con una luz y extenderlo a las tres, y finalmente programar el ciclo ajustable para una y extenderlo a las tres. Alternativamente, podra seguir con una sla luz y obtener el manual de paso para funcionar, seguido del ciclo ajustable, y luego finalmente expandirlo al control independiente de las primeras dos luces y luego a las tres luces. 5. Una vez el programa est escrito, testeado y funcionando, dibujae un diagrama de clases UML, mostrando el panel de control maestro, los displays de luces de trfico, las clases de luces de trfico y cualesquiera otras clases significativas y sus relaciones entre clases, y la secuencia de diagramas mostrando la operacin para cada uno de los casos. 6. Prepare una breve descripcin sobre cmo el programa funciona e identifique los principios OO que ha includo.

Aprendiendo OOP con Delphi

Pgina 12 de 12

Vous aimerez peut-être aussi