|
Introducción a la POO en Visual FoxPro : Conceptos
básicos (y II) Por razones de espacio se interrumpió en el número anterior el relato de los elementos y conceptos de la programación orientada a objeto y su implementación en Visual FoxPro 3.0. En este artículo continuaremos con esa descripción. En el artículo anterior intentamos explicar los conceptos de Clase, Objeto, Encapsulación, Propiedad, Método, Mensaje, Operador This, Ocultación, Polimorfismo y Evento. Pero dejamos para este artículo uno de los principios básicos de la programación orientada a objeto, la Herencia. Herencia. La herencia es un mecanismo que nos va a permitir reutilizar nuestro código de forma fácil y ordenada. Siempre que hemos definido una clase en los ejemplos anteriores hemos heredado de la clase base CUSTOM. No hemos sido conscien tes de ello, pero siempre hemos puesto la cláusula AS CUSTOM, es decir, estabamos heredando de la clase CUSTOM. Para verlo más claramente vamos a crear una clase llamada Prueba y utilizaremos la orden DISPLAY OBJECT par a ver como esta formado un objeto de esta clase: oPrueba = CREATEOBJECT( "Prueba" ) DISPLAY OBJECTS DEFINE CLASS Prueba AS CUSTOM ENDDEFINE Podremos obtener una salida de DISPLAY OBJECS similar a la figura 1. Object: OPRUEBA Priv O PRUEBA
Class Tree:
PRUEBA
CUSTOM
Properties:
BASECLASS C "Custom"
CLASS C "Prueba"
CLASSLIBRARY C "C:\TMP\prueba.FXP"
COMMENT C ""
HEIGHT N 17 ( 17,00000000)
LEFT N 0 ( 0,00000000)
NAME C "Prueba1"
PARENTCLASS C "Custom"
PICTURE C ""
TOP N 0 ( 0,00000000)
WIDTH N 100 ( 100,00000000)
Methods and Events:
ADDOBJECT DESTROY ERROR
INIT REMOVEOBJECT SAVEASCLASS
Podemos observar en primer lugar el objeto se llama OPRUEBA y es de tipo PRUEBA. En la sección Class Tree vemos que esta clase tienen un árbol de herencia compuesto de PRUEBA y CUSTOM. Así mismo vemos un buen n úmero de propiedades y algunos métodos que nosotros no hemos escrito y esto se debe ha que al haber definido la clase PRUEBA como una clase heredada de la clase CUSTOM hemos heredado las propiedades y métodos de esta clase padre. En VFP siempre es necesario crear una clase heredando de alguna otra. De esta forma, cualquier clase que creemos tendrá siempre algunas propiedades y algunos métodos que toda clase de VFP posee. La clase CUSTOM sólo posee los métodos y propiedade s que siempre podremos encontrar en todo objeto, todas las demás clases predefinidas en VFP (un total de 33) tiene muchas más propiedades y métodos que los descritos aquí, pero siempre tienen estos métodos y propiedades básicos. Sigamos con los ejemplos de la clase Persona. Definimos en su momento esta clase, pero ahora nos damos cuenta que debemos hacer una ampliación. En la aplicación necesitamos tratar también un caso concreto de persona, los contribuyentes, pero sabe mos que no todas las personas son contribuyentes, pero todos los contribuyentes son personas. Tras un cierto análisis concluimos que es necesaria la herencia para incluyendo nuevos datos y métodos de la clase Contribuyente pero manteniendo la integridad y funcionalidad de la clase Persona. Veamos como se hace esta herencia: * Clase original
DEFINE CLASS persona AS CUSTOM
cNombre = ""
cApellidos = ""
dFechaNacimiento = {}
cEstadoCivil = "Soltero"
PROCEDURE Nacer
LPARAMETER cNombre, ;
cApellidos, ;
dFecha
This.cNombre = cNombre
This.cApellidos = cApellidos
This.dFechaNacimiento = dFecha
ENDPROC
ENDDEFINE
* Clase nueva
DEFINE CLASS Contribuyente AS Persona
cNIF = ""
nBaseImponibe = 0
cRegimenSS = ""
ENDDEFINE
Cuando definamos un objeto de la clase Contribuyente, podremos hacer uso tanto de las propiedades definidas en su clase como en las definidas en las clases superiores. Así podremos decir : oContr = CREATEOBJECT( "Contribuyente" ) * Propiedades definidas * en la clase Persona oContr.cNombre = "Juan" oContr.cApellidos = "López Garrido" * Propiedad definida * en la clase Contribuyente oContr.cRegimenSS = "Autonomo" Como todo Contribuyente es también una Persona, tiene todos los métodos y propiedades de la clase Persona. La herencia nos va a facilitar enormemente el mantenimiento del código. Por una parte podemos heredar en vez de modificar la clase base y de esa forma preservar la naturaleza de los objetos sin necesidad de modificar cientos de programas. Por otra aparte, cualquier modificación que realicemos en una clase, se ve reflejada automáticamente en todas las clases que hereden de ella. Por ejemplo, si añadimos una nueva propiedad a la clase Persona o modificamos alguno de sus métodos, esa modificación también se ve reflejada en el comportamiento de los objetos Contribuyente, pues también son del tipo Persona. Identificar Clases y Herencias. La programación orientada a objeto exige de nuevas técnicas de análisis orientada objeto. Cuando nos enfrentamos a un programa realizado bajo estas técnicas la mayores dificultades las tenemos en identificar la clases correctamente y en definir las relaciones entre las distintas clases. No es fácil al principio, pero en poco tiempo la definición de clases y las herencias entre ellas será un trabajo tan sencillo como ahora identificar cuando debemos hacer una subrutina. Sobreescribir métodos o propiedades. En algunos casos, en la clase hija, queremos modificar el comportamiento de algún método o el contenido de alguna propiedad de la clase padre. Este hecho se denomina sobreescribir. Con él somos capaces de modifi car los miembros de una clase padre sin afectar al código de la misma. En el siguiente caso la clase Persona tiene un método denominado Imprimir y la clase Contribuyente va a sobreescribir este método con el suyo propio. DEFINE CLASS persona AS CUSTOM
cNombre = ""
cApellidos = ""
dFechaNacimiento = {}
cEstadoCivil = "Soltero"
PROCEDURE Imprimir
? "Nombre : " ;
+ This.cNombre ;
+ " " ;
+ This.cApellidos
? "Fecha de nacimiento : " ;
+ DTOC( This.cFechaNacimiento )
? "Estado civil : " ;
+ This.cEstadoCivil
ENDPROC
ENDDEFINE
* Clase nueva
DEFINE CLASS Contribuyente AS Persona
cNIF = ""
nBaseImponible = 0
cRegimenSS = ""
PROCEDURE Imprimir
? "Nombre : " ;
+ This.cNombre ;
+ " " ;
+ This.cApellidos
? "Fecha de nacimiento : " ;
+ DTOC( This.dFechaNacimiento )
? "Estado civil : " ;
+ This.cEstadoCivil
? "NIF : " ;
+ This.cNIF
? "Base Imponible : " ;
+ STR( This.nBaseImponible )
? "Regimen de la S.S. : " ;
+ This.cRegimenSS
ENDPROC
ENDDEFINE
Cuando llamemos al método imprimir, dependerá de la clase de objeto que utilicemos se llamará a un método o a otro. oPrue1 = CREATEOBJECT( "Persona" ) oPrue1.cNombre = "Juan" oPrue1.cApellidos = "López Garrido" oPrue2 = CREATEOBJECT( "Contribuyente" ) oPrue2.cNombre = "Pedro" oPrue2.cApellidos = "Goméz Iriarte" oPrue1.Imprimir()&& De persona oPrue2.Imprimir()&& De contribuyente Cuando escribíamos un método denominado Init o Destroy lo que estamos haciendo es sobreescribir el método por defecto para este evento. De igual forma podemos sobre escribir cualquier otro método o evento de una clase. Recordemos lo que decíamos antes sobre el Polimorfismo, este es otro ejemplo de esta característica, tenemos dos métodos con el mismo nombre en clases diferentes, pero en este caso, las clases se heredan una de otra, sobreescribiendo este método. El Operador ::. Si es observador se habrá percatado que en ejemplo anterior estamos duplicando parte del código del método Imprimir de la clase Persona en el método imprimir la clase Contribuyente, esto no parece muy acertado para ayudar al mantenimiento del código. En algunos casos queremos sobreescribir totalmente el método de la clase padre, pero en otros casos lo que deseamos en sólo incluir nuevas prestaciones, pero manteniendo el código anterior. Para estos casos se ha creado el operador :: u operador de resolu ción de alcance. Con el podemos hacer referencia al método de una clase superior aun cuando este método se hubiera sobreescrito. Para hacer uso de este operador debemos indicar el nombre de la clase padre, el operador ::, y el nombre del método. Haciendo uso de este operador podríamos haber escrito la clase Contribuyente de la siguiente forma : DEFINE CLASS Contribuyente AS Persona cNIF = "" nBaseImponible = 0 cRegimenSS = "" PROCEDURE Imprimir * Llamada al procedimiento * original de la clase Persona Persona::Imprimir() && OPERADOR :: * Resto de impresión ? "Estado civil : " ; + This.cEstadoCivil ? "NIF : " ; + This.cNIF ? "Base Imponible : " ; + STR( This.nBaseImponible ) ? "Regimen de la S.S. : " ; + This.cRegimenSS ENDPROC ENDDEFINE Esta característica nos asegura un buen mantenimiento de nuestras clases, pues cualquier modificación en el método de la clase padre, se ve automáticamente reflejado en el método de la clase hija, aunque este sobre escríto el método. Conclusiones Han sido muchos conceptos seguidos y es posible que se sienta un poco aturdido con tantas palabras. No se preocupe, como decíamos al principio, la POO es un conjunto de conceptos interrelacionados que difícilmente se entiende unos sin los otros. Poco a po co irá comprendiendo su significado y concluirá que no están difícil como algunos quieren hacer creer. En los siguientes artículos haremos referencia a los conceptos aquí esbozados y esperemos que se vaya encontrando más fácil su compresión a medida que avancemos. En este artículo solo hemos creado clases partiendo de CUSTOM y siempre han sido clases muy poco prácticas, pero prometemos realizar algunas clases que si merecen la pena ser utilizadas. También describiremos las herramientas que VFP nos da para una hacer más fácil la POO, las clases que nos facilita y el modo de trabajar con ellas. La POO está aquí y no deberíamos ignorarla por más tiempo. Posiblemente no es necesario este tipo de programación, pero es realmente muy recomendable, es seguro que no soluciona todos los problemas, pero es mucho más sencillo el desarrollo, tendremos que esforzarnos un poco al principio, pero nuestro esfuerzo se verá sobradamente recompensado. En definitiva, la Programación Orientada a Objeto es una mejor forma de programar.
|