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.