Introducción a la POO en Visual FoxPro: Conceptos básicos
12/04/2020
Introducción a la POO en Visual FoxPro: Conceptos básicos
Con este artículo empezamos una pequeña serie que nos acercará a la Programación Orientada a Objeto (POO) en Visual FoxPro. En este, el primero, haremos una descripción de los conceptos básicos sobre los que se sustenta la POO a fin de introducirnos en esta forma de programar.
Si Vd. es nuevo en esto de la POO es posible que en algunos momentos se sienta perdido. No se preocupe, es normal. La orientación a objeto tiene un gran número de conceptos interrelacionados, por ello cuando describimos uno de forma separada parece carecer de sentido. No dude en releer el texto una y otra vez, de esa forma conseguirá tener una visión de conjunto.
Si por el contrario ya lleva algún tiempo aplicando estas técnicas lo que le interesa realmente es la implementación que de estas técnicas de programación hace Visual Foxpro (VFP), en parte podrá ver algunas de sus características en este artículo, pero e s realmente en los próximos donde se desarrollará toda la potencia de VFP. Hemos intentado en todo momento ser rigurosos, pero espero que disculpe que en algunas ocasiones se haga una descripción poco formal de las características de la POO a fin de facilitar la compresión.
Si ya conoce algunas de las características de VFP sabrá que es posible realizar POO casi sin darse cuenta, pero en este artículo haremos hincapié en los entresijos de este tipo de programación desde la codificación más tradicional, a fin de saber perfectamente lo que estamos haciendo en cada momento. En próximos artículos describiremos la POO cuando utilizamos Form Designer, el generador de pantallas, o Class Designer, la herramienta visual para definición de clases, así como el manejo de herramientas de apoyo como el Class Browser.
¡No es tan difícil!
Está de moda hablar de POO, y no es nuevo este concepto, pero es ahora cuando se está generalizando su uso. Esta moda ha provocado alguna confusión, muchas herramientas han dicho estar orientadas a objeto sin serlo, otras han realizado implementaciones muy extrañas, y algunas, como VFP, han sabido mantener un equilibrio y una buena implementación, por lo que son verdaderamente recomendables para desarrollar con este nuevo sistema de programación.
Quizás uno de los problemas a la hora de afrontar por primera vez la POO, estriba en el temor que puede producir tantos y tantos nuevos conceptos que se ciernen sobre nosotros. Lo cierto es que la POO no es tan fiera como la pintan, pero en la medida que cambia algo nuestra forma de pensar los programas, sus componentes y el modo que se relacionan entre si, requiere un cierto esfuerzo.
Los que llevan años en esto de la programación recuerdan lo difícil y extraño que fue el paso de la programación lineal (con goto) a la programación estructurada (con call), ahora toca el paso de la programación estructurada a la POO. La POO viene a completar algunas de las lagunas de la programación estructurada, como esta vino a solucionar algunos de los problemas de la programación lineal. No es la soluciona a todos nuestros problemas, pero sí facilita nuestra labor de programación y mantenimiento.
Objetivos de la PPO
La POO intenta ser un mejor sistema para el desarrollo de aplicaciones. Como toda técnica de programación si se hace mal puede ser desastrosa -pensemos los líos que se pueden provocar si aplicamos mal la programación estructurada- pero es un mejor sistema de desarrollo.
Una de las primeras mejoras que obtenemos por el uso de la POO es el permitir afrontar programas más complejos y de mayor tamaño con menos esfuerzo. El hecho de trabajar con pequeños elementos bien definidos, como son los objetos, nos permite aislar cada componente de la aplicación del resto y de esa forma aprovechar en mayor medida nuestro esfuerzo.Una vez adiestrados en las nuevas técnicas de orientación a objeto obtendremos también una mejora considerable en nuestro rendimiento de desarrollo. Las grandes facilidades para el reaprovechamiento del código que nos ofrece la orientación a objeto harán que desarrollemos con mayor velocidad, pero manteniendo unos buenos niveles de calidad.
La reutilización de código en la POO nos otorga una gran flexibilidad. La existencia de la herencia permite modificar las características que necesitemos de una clase de objeto con seguridad de no alterar las especificaciones del mismo y aprovechan do todo el desarrollo realizado en el mismo.El mantenimiento de aplicaciones se ha visto como uno de los grandes problemas de la programación actual. Con las técnicas de POO es más sencillo realizar este mantenimiento. Los objetos son elementos de pequeño tamaño, bien definidos, y por lo tanto más fáciles de mantener. Además la existencia de la herencia nos va a asegurar que la modificación de algunas características de la clase no van ha afectar a los desarrollos ya terminados.
La definición correcta de los objetos permitirá tener una mejor estructuración de nuestros programas. Con la POO se tiene un acercamiento más natural a los problemas y por lo tanto los análisis de aplicaciones orientadas a objeto tienen un acercamiento a la realidad mucho más completa que con la programación estructurada. Esto no quiere decir que al principio no cueste un poco realizar este tipo de análisis, pero una vez adquirida las nuevas técnicas, es mucho más sencillo.
La POO es muy fácilmente compresible en entornos gráficos, pues el hecho de que estos entornos manejen objetos gráficos hace muy recomendable este tipo de programación. Pero debemos tener claro que es totalmente posible realizar POO en entornos de tipo carácter y es posible programar entornos gráficos sin POO.
¡No todo son ventajas!
Tampoco podemos decir que todo sean facilidades. Por una parte la POO nos va ha obligar a cambiar nuestra forma de pensar los programas y por lo tanto es necesario un tiempo para que las nuevas técnicas se nos hagan habituales y automáticamente nos salga programar de esta forma.
No basta con dominar estas técnicas para que alcancemos las mejores cotas de productividad, posiblemente sea necesario elaborarnos unas buenas librerías de clases. Para ello es conveniente desarrollar un par de aplicaciones bajo esta técnica para darnos cuenta de que es lo que realmente es factible de ser reutilizado y de esa forma organizar nuestras propias librerías de clases.La depuración de código orientado a objeto es algo más compleja que la depuración de código estructurado. Esto no quiere decir que nuestro código va a ser peor o va ha tener más errores, pero sí es cierto que en el caso de producirse un error deberemos re correr todo el árbol de herencia para encontrarlo, algo que en programación estructurada no tenemos que hacer.
Pero en general, podemos decir que los inconvenientes son realmente menores que las ventajas, y por lo tanto es realmente recomendable el desarrollo con POO.
Elementos de la POO
Vamos a hacer un repaso de los distintos elementos que componen la POO, y como cada uno de ellos es implementado en VFP. En algunos momentos es posible que no entienda algunos de los conceptos, no se preocupe, poco a poco intentaremos aclarar todo este entramado de conceptos y sintaxis. Clase y Objeto. Son los dos primeros elementos de la POO. Se puede decir que la clase es la generalización de los objetos y los objetos son la concreción de la clase, aun cuando parezca un galimatías.
Se suelen poner ejemplos bastante filosóficos para describir la relación entre el objeto y la clase (variando según sea el autor idealista o empirista). Podemos decir que existen un objeto Pedro, otro Antonio y otro Luisa. Todos estos objetos tienen elementos en común y por ello decimos que son de la clase persona. De la misma forma otros dicen que tenemos una idea clara de lo que es ser una persona y luego somos capaces de distinguir hombres concretos.
Para poner otro tipo de ejemplo podemos observar los botones de un entorno gráfico. Todos sabemos como es un botón en un entorno gráfico, si bien cada botón es diferente de los demás, todos se parecen y por lo tanto podemos decir que la clase botón es la generalización de las propiedades y comportamientos de todos los botones de los entorno gráficos.
Definir una clase. Las clases son la descripción de los elementos comunes de los objetos que generalizan. Así las clases se definen y pueden ser usadas para crear innumerables objetos de este tipo. Para definir una clase utilizaremos una sencilla sintaxis de VFP. Vamos a empezar definiendo una clase denominada Persona. Es una clase vacía, pero nos sirve para comenzar:DEFINE CLASS Persona AS CUSTOM
ENDDEFINEPara poder utilizar esta definición debemos incluirla en un fichero .PRG y cargado con SET PROCEDURE TO o bien incluirla al final de nuestro fichero .PRG.
Crear un objeto. Ya podemos crear objetos basado en esta clase, para ello utilizamos la siguiente expresión:oPersona1 = CREATEOBJECT( "Persona" )
oPersona2 = CREATEOBJECT( "Persona" )Debemos tener clara la diferencia entre clase y objeto. La clase es una plantilla, donde definimos las características de cada uno de los objetos. Los objetos que hemos creado, oPersona1 y oPersona2, comparten la plantilla que con la que se han creado, la clase Persona, pero son diferentes entre si.
Borrar un objeto. Los objetos se asemejan a las variables en cuanto a que pueden declararse como LOCAL, PRIVATE o PUBLIC. Esta es la única similitud que tienen las variables y los objetos, pero es una característica muy importante.
Por defecto, los objetos son de tipo PRIVATE y por lo tanto existirán mientras se ejecute el programa que los creó. Una vez salgamos de este programa el objeto se borrará automáticamente. El objeto puede ser usado en el programa que lo creó y en todos lo programas llamados desde él.
Si declaramos el objeto como LOCAL el objeto persistirá hasta la salida del programa que lo creó, pero los programas que sean llamados desde el programa de creación no podrán hacer uso de este objeto, pues permanece oculto par a ellos, evitando así posibles problemas con los nombres de los objetos.
Al declarar un objeto como PUBLIC estamos indicando que permanezca hasta que lo borremos explícitamente o salgamos de VFP. Este objeto podrá ser utilizado por cualquier programa desde el momento que es creado hasta que sea borrado.
Para borrar un objeto de forma explícita debemos hacer uso del comando RELEASE. Podemos borrar de forma explícita no solo los objetos públicos, sino también los locales y privados.
Veamos un ejemplo muy simple de creación de un objeto público y su destrucción:
PUBLIC oPrueba
oPrueba = CREATEOBJECT( "Persona" )
...
RELEASE oPruebaEncapsulación. Aunque parece un termino extraño, es muy habitual en POO. Hace referencia a la capacidad de los objetos para incluir dentro de si tanto datos como acciones. Las clases de distinguen unas de otras justamente por tener unos datos y acciones que las diferencian. Los objetos de una misma clase se diferencian entre si por tener datos diferentes.
Los datos que caracterizan a una clase se denominan propiedades y sus acciones (o programas) se denominan métodos. Se dice por lo tanto que la clase encapsula métodos y propiedades, es decir, que agrupa dentro si tanto métodos como propiedades.
Las propiedades y métodos de una clase se denominan habitualmente propiedades y métodos miembro.
Veamos poco a poco estos nuevos conceptos.
Propiedades. Como hemos dicho, las propiedades son los datos que manejan las clases. Estas propiedades se declaran en la definición de la clase y permanecen en todo momento asociados a los objetos creados bajo esa clase.
Para verlo con más claridad vamos a dar contenido a esta definición de la clase Persona que iniciamos hace un momento. Para ello debemos estudiar que propiedades posee este tipo de objeto. Podemos decir que todas la personas tienen un nombre, unos apellidos y una fecha de nacimiento. Hay muchas otras propiedades para una clase de este tipo, pero empecemos con estas. La implementación en VFP se haría de la siguiente manera:
DEFINE CLASS persona AS CUSTOM
cNombre = ""
cApellidos = ""
dFechaNacimiento = {}
ENDDEFINEEn nuestra definición de clase declaramos las propiedades con unos valores iniciales, que pueden ser de cualquiera de los tipos de datos definidos en VFP. Por ejemplo, cNombre y cApellidos los hemos inicializado como una cadena vacía y dFechaNacimiento como una fecha también vacía, pero pueden ser numéricos, datetime o de cualquier otro tipo.
También podríamos haber definido las propiedades con cualquier otro tipo de valor por defecto: una texto o una fecha en concreto, de esta forma, al crear un objeto ya tendría este valor la propiedad.Por ejemplo podríamos ampliar la definición de la clase incluyendo una propiedad denominada cEstadoCivil que por defecto fuera la cadena Soltero.
DEFINE CLASS persona AS CUSTOM
cNombre = ""
cApellidos = ""
dFechaNacimiento = {}
cEstadoCivil = "Soltero"
ENDDEFINEUna vez creado un objeto, si quedemos dar valores a cada una de sus propiedades haremos uso de operador punto. Para ello pondremos el nombre del objeto, un punto y el nombre de la propiedad:
oPersona1 = CREATEOBJECT( "persona" )
oPersona1.cNombre = "María"
oPersona1.cApellidos = "Pérez González"
oPersona1.dFechaNacimiento = {20-10-75}oPersona2 = CREATEOBJECT( "persona" )
oPersona2.cNombre = "Pedro"
oPersona1.cApellidos = "Jiménez Nieto "
oPersona2.dFechaNacimiento = {04-12-69}
oPersona2.cEstadoCivil = "Casado"Si, como en este caso, vamos a dar valores a muchas propiedades de un objeto podemos utilizar la sintaxis abreviada, de la forma siguiente:
WITH oPersona2
.cNombre = "Pedro"
.cApellidos = "Jiménez Nieto "
.dFechaNacimiento = {04-12-69}
.cEstadoCivil = "Casado"
ENDWITHDesde ese momento podemos hacer uso de estas propiedades, usando también el operador punto:
WAIT WIND oPersona1.cNombre
WAIT WIND oPersona2.cNombreComo decíamos, las propiedades están asociadas a cada objeto, de esta forma el valor de la propiedad cNombre es diferente entre los objetos oPersona1 y oPersona2, aun cuando en la definición de la clase hubiéramos otorgado un va lor por defecto a esta propiedad. Es importante tener clara esta diferencia. Las propiedades se declaran en la definición de la clase, pero los valores de las propiedades pueden ser diferentes para cada uno de los objetos de esta clase.
Métodos. El otro elemento característico de una clase son los métodos. Los métodos son acciones que pueden realizar los objetos, es decir, son funciones o procedimientos asociados a este tipo objeto.
En el caso de las personas podemos decir que pueden nacer, morir, casarse, tener hijos, etc... Para dar un primer ejemplo de esto veamos uno de sus métodos :
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
ENDPROCENDDEFINE
En este método damos valor a tres propiedades del objeto a partir de los parámetros que se nos han pasado. Quizás sea todavía poco evidente este código, pero poco a poco iremos entendiéndolo.
En primer lugar debemos diferenciar entre los parámetros de este método y las propiedades. Los parámetros son variables y se perderán al finalizar la ejecución del método. Las propiedades, que son la que empiezan con la sintaxis This., permanecen mientras dure la existencia del objeto.
Por otra parte, este procedimiento, denominado Nacer, se diferencia de los procedimientos que estamos acostumbrados a escribir en que sólo es llamable asociado a un objeto de la clase Persona y no puede ser invocado de forma independiente. Esta es una de las grandes diferencias entre la programación estructurada y la POO.
Mensajes. Cuando llamamos a un método de un objeto se dice que estamos enviando un mensaje al objeto para que realice una determinada acción. Así cuando enviamos un mensaje de nacer a un objeto persona estamos ejecutando el método correspondiente:
oPersona1 = CREATEOBJECT( "Persona" )
oPersona1.Nacer( "María", ;
"Pérez González", ;
{20-10-75} )
? oPersona1.cNombre
? oPersona1.cApellidos
Operador THIS
Vemos dentro del código del método Nacer que hemos utilizado una extraña sintaxis, algo como THIS. Decíamos al principio que la clase es una plantilla y cuando definimos un método dentro de esta plantilla que es la clase no sabemos cual será el nombre del objeto que utilizará este método, por eso, cuando vamos a utilizar una propiedad o un método de la clase, debemos anteponer al operador punto el operador This, para indicar que se trataran las propiedades del objeto que recibe el mensaje, es decir, que ha sido invocado, y no para otro.
Decíamos que las propiedades mantienen valores diferentes para cada uno de los objetos, pero los métodos comparten su código entre todos los objetos de una clase. Un método varía en la medida que las propiedades del objeto que lo llama son diferentes, por ello es tan importante el operador This.
De esta forma cuando ejecutamos:
oPersona1.Nacer( "María", ;
"Pérez González", ;
{20-10-75} )el código del método Nacer asocia el primer parámetro, denominado cNombre, a la propiedad cNombre del objeto que recibe el mensaje. Así This.cNombre es una propiedad del objeto y cNombre es una variable que corresponde al primer parámetro del método invocado en el mensaje.
PROCEDURE Nacer
LPARAMAMETER cNombre, ;
cApellidos, ;
cFechaThis.cNombre = cNombre
...ENDPROC
Ocultación. Una de las mejoras que implementa la POO, es la posibilidad de limitar el acceso a determinadas propiedades o métodos. Con ello conseguimos que la utilización de la clase se haga de forma ordenada.
Las propiedades o métodos protegidos sólo son utilizables desde los métodos pertenecientes a esta clase y no pueden usarse directamente por otros programas. Vamos con un ejemplo:DEFINE CLASS Nivel AS CUSTOM
PROTECTED nContador
nContador = 0PROCEDURE Mas
LPARAMETER nCantidad
This.nContador = This.nContador ;
+ nCantidad
ENDPROCPROCEDURE Menos
LPARAMETER nCantidad
This.nContador = This.nContador ;
- nCantidad
ENDPROCPROCEDURE Ver
RETURN This.nContador
ENDPROCENDDEFINE
Esta clase define un interface por medio de los métodos Mas, Menos y Ver. Si intentamos modificar el valor de nContador de forma directa nos dará un error, pues esta propiedad está protegida.
oTemperatura = CREATEOBJECT( "Nivel" )
oTemperatura.Mas ( 10 )
? oTemperatura.Ver()
oTemperatura.Menos ( 3 )
? oTemperatura.Ver()* Provoca el Error :
* Property NCONTADOR is not found.
oTemperatura.nContador = 100Al igual que podemos proteger propiedades, podemos proteger métodos. De esta forma podemos definir métodos que sólo sean usado por otros métodos de la clase y no puedan ser invocados a partir de los objetos de esta clase. Para ello basta colocar la cláusula PROTECTED antes de PROCEDURE.
Polimorfismo. Cuando realizamos programación estructurada debemos tener cuidado de no llamar con el mismo nombre a dos variables o a dos procedimientos, sin embargo en la POO podemos llamar a un método o a una propiedad de una clase de igual forma que un método o propiedad de otra. Esta característica es lo que se denomina polimorfismo. Veamos un ejemplo con propiedades:
DEFINE CLASS ClaseUno AS CUSTOM
Dato = 1000
ENDDEFINEDEFINE CLASS ClaseDos AS CUSTOM
Dato = "Hola"
ENDDEFINEEsto, que puede parecer un problema, es muy sencillo de entender si vemos como los utilizamos los dos objetos :
Objeto1 = CREATEOBJECT( "ClaseUno" )
Objeto2 = CREATEOBJECT( "ClaseDos" )? Objeto1.Dato
? Objeto2.DatoAun cuando en los dos objetos llaman a una propiedad denominada Dato, en el primer caso estamos llamando a una propiedad tipo numérica definida en ClaseUno, y el segundo caso estamos llamado a una propiedad de tipo carácter definida en ClaseDos.
Igual que las propiedades, en el caso de los métodos también es posible el polimorfismo. De esta forma podemos definir dos métodos denominados Imprimir, pero que cada uno hace una cosa bien distinta:
DEFINE CLASS ClaseUno AS CUSTOM
PROCEDURE Imprimir
? "Hola esto es una prueba ..."
ENDPROC
ENDDEFINEDEFINE CLASS ClaseDos AS CUSTOM
PROCEDURE Imprimir
LIST STATUS
ENDPROC
ENDDEFINEEn VFP no es posible definir dos métodos o dos propiedades con igual nombre dentro de una misma clase.
Eventos. Existe una serie de métodos especiales, que normalmente no se ejecutan por ser invocados de forma explícita, como los que hemos definido hasta ahora, sino que por denominarse de una forma determinada son lanzados cuando 'pasa algo', es decir, cuando se produce un evento. Estos eventos pueden ser un click, el movimiento del ratón, un pulsación de tecla, etc..
Los dos primeros eventos que vamos a tratar son los denominados Init y Destroy. El primero se ejecuta cuando se crea un objeto de esta clase y el segundo cuando se destruye. En otros lenguajes orientados a objeto estos métodos son denominados el constructor y el destructor de la clase, pero el VFP son tratados como eventos. Los eventos Init y Destroy los encontramos en absolutamente todos los objetos de VFP. Veamos como hacer uso de esta característica creando dos métodos con estos nombres:
* Lanzamiento automático del
* método INIT
oPrueba = CREATEOBJECT( "Eventos" )
...
* Lanzamiento automático del
* método DESTROY
RELEASE oPruebaDEFINE CLASS Eventos AS CUSTOM
PROCEDURE Init
? "Creación del objeto ..."
ENDPROCPROCEDURE Destroy
? "Destrucción del objeto ..."
ENDPROC
ENDDEFINECada tipo clase tiene unos eventos predefinidos y que no podemos añadir más. Así, por ejemplo, la clase CUSTOM tiene definidos los eventos Init, Destroy y Error, la clase COMMANDBUTTOM los eventos Click, Destroy, DragDrop, DragOver, Error, ErrorMessage, GotFocus, Init, InteractiveChange, KeyPress, LostFocus, Message, MouseDown, MouseMove, MouseUp, RightClick, UIEnable, Valid, When, etc.. La mayoría de estos eventos los veremos en próximos artículos de forma detallada. De momento quedémonos con el concepto de evento.
Objetos como propiedades. Existe la posibilidad, en algunas clases, de definir objetos como propiedades miembro. Por ejemplo, podemos querer definir una clase matrimonio con dos objetos tipo persona. Así definiríamos:
DEFINE CLASS Matrimonio AS CUSTOM
dFecha = {}
ADD OBJECT Conyuge1 AS Persona
ADD OBJECT Conyuge2 AS Persona
ENDDEFINEPara poder hacer uso de ellos no tenemos más que indicar el nombre del objeto miembro. Por ejemplo:
oPareja1 = CREATEOBJECT( "Matrimonio" )
oPareja1.Conyuge1.Nombre = "María"
oPareja2.Conyuge2.Nombre = "Pedro"No todas las clases admiten que sean definidos objetos como miembros de la misma, sólo las clases denominadas contenedoras, entre la que se encuentra CUSTOM.
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 conscientes de ello, pero siempre hemos puesto la cláusula AS CUSTOM, es decir, estábamos 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 está formado un objeto de esta clase:
oPrueba = CREATEOBJECT( "Prueba" )
DISPLAY OBJECTSDEFINE CLASS Prueba AS CUSTOM
ENDDEFINEPodremos obtener una salida de DISPLAY OBJECS similar a la figura 1.
Object: OPRUEBA Priv O PRUEBA
Class Tree:
PRUEBA
CUSTOMProperties:
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 SAVEASCLASSPodemos 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 propiedades 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 sabemos 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 cómo 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 = ""
ENDDEFINECuando 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.
Sobrescribir 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 sobrescribir. Con él somos capaces de modificar 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 sobrescribir 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
ENDDEFINECuando 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 contribuyenteCuando escribíamos un método denominado Init o Destroy lo que estamos haciendo es sobrescribir 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, sobrescribiendo 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 sobrescribir 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 resolución de alcance. Con el podemos hacer referencia al método de una clase superior aun cuando este método se hubiera sobrescrito.
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
ENDDEFINEEsta 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 escrito 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 poco 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.
Descarga
Referencias
Recuperado: Camf