CURSO OOP en CLIPPER 5.01 (N 3)  GRUPO EIDOS             Brief by HEST


En plena locura de OOP, y en los tiempos de expectativa que  corren,  echar 
el freno y mirar a nuestro alrededor, reflexionando sobre lo  que  hacemos, 
puede  sorprender  a  ms  de  uno.  Desde el glorioso da en que recib el 
fichero  oClip  en uno de los nmeros de clippeRmana, se produjo un cambio 
radical en mi forma de programar, clases, objetos, herencia, qu felicidad, 
la piedra filosofal, haba descubierto la rueda,  al  intentar  ilustrarme, 
descubr  que la rueda ya estaba inventada hace muchos aos, pero pens que 
era el momento de seguir el camino.

Mi  primera  sorpresa  fue que mis programas eran ms lentos y ms grandes, 
requeran ms memoria, por otra parte la programacin de procesos complejos 
se simplificaba, la programacin OOP es  ms  racional  y  sobre  todo  ms 
productiva,  pero  requiere  un  cambio de planteamiento, para programar en 
OOP, no vale hacer cuatro clases y usarlas en programas estructurados, todo 
el  planteamiento  debe  ser  OOP,  me  explico,  al  hacer el anlisis del 
problema debemos hacerlo pensando en clases.  Qu  es  un  cliente?,  una 
factura?, por qu no, descompongamos un cliente:

Propiedades:
-----------
Tiene nombre
Direccin
Facturas
Saldos
.....

Como se comporta:
----------------
Compra
Nos debe dinero
Paga facturas
No nos Compra
..........


A  dnde  os  quiero  llevar?,  a  crear un CLASE CLIENTE, por supuesto, a 
simple  vista  parece algo difcil, pero empezaremos desde el primer nivel.
Podremos  crear  una  clase  editor  de  DBF bsica, de este editor podemos 
derivar  una clase algo ms compleja para la edicin de Clientes, otra para 
editar albaranes.....

En programacin OOP creamos clases sencillas, de stas  derivamos  a  otras 
ms complejas, cogemos caractersticas de otras, reescribimos mtodos, todo 
en  forma  de  rbol,  y  por  qu no?, una clase puede manejar objetos, y 
mantenerlos.  En  la aplicacin real, slo manejamos objetos, los creamos y 
les mandamos mensajes, resulta fcil y la estructura en nuestra mente no se 
pierde en funciones, manejamos objetos, algo  comn  hasta  para  un  nio, 
claro est, hay construir la CLASES.

Llegados  a  este  punto,  pienso, muy bien, he creado todas las clases que 
necesito, derivo de ellas y monto programas en corto espacio de  tiempo,  a 
ganar dinero, pero no, al despertar del sueo me encuentro con  un  cliente 
que  tiene  un AT con 1 mega, un HD de 20M a pedales, mi .EXE ocupa casi un 
mega y la luz del disco duro se funde.

Para todos aquellos que queris introduciros en la  programacin  OOP  leer 
estas lneas con atencin, esto no es el chollo del siglo, la OOP es  buena 
para  el programador, pero mala para los programas, es como una droga dura, 
si la pruebas no lo puedes dejar, como programadores nunca renunciaremos  a 
semejante  avance, pero vale la pena ir despacio, hoy por hoy el Clipper NO 
ES LA HERRAMIENTA IDEAL para programar en OOP, en el futuro, no s, slo CA 
(y Brainland que tienen los Beta), lo saben.

No es que quiera desalentar a nadie, yo actualmente desde el anlisis a  la 
codificacin,  lo  hago  en  OOP,  tarde  o temprano todos los productos de 
desarrollo  sern OOP, y hay que utilizar las nuevas tcnicas y ms las que 
son  tan  rentables,  pero  con  cuidado,  tenemos  que analizar que es ms 
rentable.

Los  objetos  internamente  para  Clipper  son  arrays,  en estos arrays se 
contiene toda la informacin de variables  y  mtodos.  Cuando  creamos  un 
objeto, Clipper crea un array esttico, cuando mandamos un mensaje, busca en 
el array y salta a la direccin donde se encuentra el cdigo, es como ir de 
Valencia a Madrid, pasando por Barcelona para comprar un mapa. Incluso en C 
es algo parecido, pero claro, C genera cdigo mquina y es inapreciable.

Para ilustrar mis conclusiones he realizado  las  siguientes  pruebas,  los 
resultado,  cada  uno  que  los  interprete como quiera y que saque conclu-
siones, y a pesar de lo pesimistas que son estas lneas, el que no programe 
en OOP, que aprenda, por si acaso.

Se  trata  de  crear 20 objetos de una clase, y ejecutar un mtodo con cada 
objeto,  las  pruebas  se  realizan  con  Class(y),  SuperClas  II,  usando 
funciones,  y con el sistema de objetos de Clipper. El mtodo incrementa un 
array,  la  memoria ocupada por los objetos es de unos 700K, para probar el 
VMM, que funcion correctamente con todos, no se hizo de mayor  tamao,  ya 
que  interesa  que el swap se realice en el disco virtual y as discriminar 
el  tiempo  de acceso a disco fsico. Los tiempos de ejecucin del programa 
fueron mnimos (los .EXE  estaban  en  el  disco  virtual  y  el  ordenador 
reinicializado para que no estuvieran en el cache) y  poco  significativos, 
debido a lo poco que hace la clase creada para la prueba, pero nos  da  una 
idea  del  factor  multiplicador  que  existir  en caso de usar Clases ms 
complejas. Los resultados fueron los siguientes:


Prueba de Velocidad de Objetos / Funciones
------------------------------------------
Segundos     :    2.37
Memoria Libre:     407

Prueba de Velocidad de Objetos Class(y)
---------------------------------------
Segundos     :    2.41
Memoria Libre:     395

Prueba de Velocidad de Objetos SuperClass II
--------------------------------------------
Segundos     :    2.47
Memoria Libre:     380

Prueba de Velocidad de Objetos Clipper
---------------------------------------
Segundos     :    2.48
Memoria Libre:     406

Estos  resultados  cada  uno  los  podr interpretar como quiera, pero algo 
queda claro, los objetos en Clipper son leeeeennnnnntoooooooossssssss.

Al iniciarse la clase, si no est creada, o lo que es lo mismo, si no se ha 
hecho uso de ella, Clipper se queja un poco, la memoria se resiente ya  que 
los objetos ocupan ms memoria (la famosa lista en un array) y la ejecucin 
de los mtodos no es directa.

Con  estas  lneas  espero  que  se  entienda  lo  que se puede sacar de la 
Programacin  Orientada  a Objetos, sus pros y sus contras, pero a pesar de 
algunos reticentes, queramos o no, tendremos que usar los objetos, y el que 
no lo haya hecho, por lo menos sabe con lo que se encontrar.

un:Saludo()

*****************************************************************************
*****************************************************************************
A la hora de hablar de objetos pensamos en ellos de una forma muy concreta, 
que corresponde a la estructura lgica con la que vemos los  objetos  y  la 
relacin  entre  las  variables  y  los  mensajes,  sin embargo nos podemos 
plantear el tener dos tipos de estructuras que corresponden a la estructura 
lgica y la estructura fsica.

En  nuestra  forma  de  concebir los objetos a travs de oClip, SuperClass, 
Dialog,  etc.  no  tenemos  en  cuenta  la estructura fsica porque van tan 
relacionados  que  su correspondencia es exacta, o lo que es lo mismo, cada 
variable  declarada  en  un objeto se corresponde con un elemento del array 
que Clipper crea internamente y en el mismo orden en que se definen.


EL OBJETO TBROWSE POR DENTRO

Antes de ver qu diferencias podra haber entre los  tipos  de  estructuras 
vamos a ver cmo Clipper ejecuta internamente sus objetos TBrowse.

Vamos a ver esta estructura interna a travs del tratamiento de  un  objeto 
como si fuera un array.

1    C    "VALOR DE CARGO"
2    A    { 5 ,5 ,20, 75 }
3    A    { oTBcolum1, oTBcolum2, ....... }
4    C    " B/N, N/B, N, N, N "
5    A    { "=", " ", " " }
6    B    { | | DBGoTop }
7    B    { | | DBGoBottom }
8    B    { |x| DBSkip(x) }
9    C    ???

- En el primer elemento del array tenemos siempre un valor  NIL  o  lo  que 
  definamos para la variable cargo.

- En el segundo  elemento  obtenemos,  a  su  vez,  otro  array  de  cuatro 
  elementos numricos que corresponden a los valores que se han pasado como 
  coordenadas de la ventana al definir el objeto.

- En la tercera posicin obtenemos un array con todos los objetos  TBcolumn 
  que se han definido para objeto TBrowse.

- En la cuarta aparece un elemento de tipo carcter con  la  definicin  de 
  los colores.

- En la posicin quinta hay un array de tres posiciones  tipo  carcter  en 
  los que se almacenan los caracteres por defecto para la definicin de los 
  separadores entre columnas y cabecera.

- La posicin sexta, sptima y octava son los CodeBlocks que corresponden a 
  los CodeBlock de GoTop, GoBottom y Skip.

- La posicin novena es de tipo carcter y, entre otras cosas,  guarda  los 
  datos de la estabilizacin y el contenido de la ventana correspondiente a 
  ese objeto, as como una serie de cosas ms, internas del propio objeto.

Una  vez analizada la estructura fsica del objeto podemos comprobar que no 
corresponde apenas con la forma lgica del objeto.

A simple vista no parece que haya ninguna ventaja en  tener  dos  tipos  de 
estructuras  y  que, incluso, puede dar lugar a equivocaciones, pero lo que 
est  claro  es  que  los  dos tipos de estructuras, a priori, da mucha ms 
flexibilidad  al  programador.  El  usuario  no  tiene  por  qu  saber  la 
estructura  fsica, sino slo la lgica, dando potencia al programador para 
efectuar  otro  tipo  de organizacin interna ms lgica para programar los 
objetos y una organizacin externa ms lgica para utilizarlos.

Vamos  a  ver  detenidamente  algo que parece incoherente dentro del objeto 
TBrowse; la segunda posicin, donde hay un array  con  cuatro  coordenadas. 
Esta estructura no parece lgica que est  dentro  de  un  array,  pero  si 
pensamos detenidamente y tenemos en cuenta que cuando se pasa un array como 
parmetro,  o en asignacin, slo se pasa su referencia no su contenido, de 
un plumazo, se pasan las  cuatro  coordenadas  ahorrando  hasta  tiempo  de 
ejecucin  en  paso  de parmetros y, adems, ser raro que se necesite una 
sola por separado, sino las  cuatro  en  conjunto.  Pero  tambin  est  la 
posibilidad de poner una sola, haciendo referencia a esa posicin.

Otra  cuestin  a  tener  en  cuenta  es que no vemos por ninguna parte una 
posicin, dentro del array del objeto, que corresponda  a  la  variable  de 
instancia ColCount.


CONCLUSION

Despus  de  ver  estas  peculiaridades  del  objeto  TBrowse slo cabe una 
explicacin; dentro de las definiciones de objetos existe  la  posibilidad, 
primera,  de  poder  definir  nosotros  los mtodos de acceso (asignacin y 
captura) de los valores de las variables de instancia para poder establecer 
la  relacin  entre  la  forma  lgica  y  la  forma  fsica y, segunda, la 
posibilidad de definir variables de instancia lgicas sin  que  tengan  que 
corresponder  con  ninguna  fsica  como  es  el  caso  de  ColCount,   que 
seguramente devuelva:

Len ( oTBrowse [3] )


ELUCUBRACIONES SOBRE SINTAXIS

En la definicin de mtodos,  que  actualmente  conocemos,  ya  tenemos  la 
posibilidad de definir el mensaje como lgico en  relacin  con  el  mtodo 
fsico  que  se ejecuta de una forma lgica y sencilla. Sin embargo, en las 
variables, esto se complica un poco ms puesto que  tenemos  que  tener  en 
cuenta tres cosas:

1- Mtodo o funcin de asignacin.
2- Mtodo o funcin de devolucin de valor.
3- Si es variable lgica.

Sin que sea obligatorio especificar ninguna de estas tres opciones.

La sintaxis que se me ocurre podra ser:

VAR <NameVar> [SET <FunctionSet>] [GET <FunctionGet] [LOGICAL]

NameVar es el nombre lgico de la variable de instancia.

FunctionSet  es  el  mtodo  o  la  funcin  de  asignacin.  Da valor a su 
correspondiente variable fsica.

FunctionGet  es  el  mtodo  o funcin encargada de devolver el valor de la 
variable fsica y la opcin.

LOGICAL para definirla slo como lgica.

Haciendo un peque o parntesis, quiero  exponer  que  la  notacin  que  he 
elegido a la hora de definir las clases es la que utiliza SuperClass porque 
es probable que CA haya tomado esa misma sintaxis, aunque lo importante  no 
es la sintaxis sino el concepto de creacin de clases, y es lo que pretendo 
explicar aqu.

Siguiendo con lo que  estbamos,  de  esta  forma,  podemos  conseguir  que 
nuestra definicin lgica no corresponda con la estructura fsica y  adems 
existan variables lgicas que no tengan ninguna relacin con alguna fsica.

As,  es  el  programador el que define su clase de acuerdo a dos formas de 
pensar,  en  la construccin de la clase y en su utilizacin. Eso s, es el 
programador el que toma la responsabilidad de que los mtodos  o  funciones 
SET y GET sean correctas, teniendo en cuenta  que  si  las  definimos  como 
mtodo vamos a tener automticamente a nuestra disposicin la  opcin  self 
para  acceso  al  objeto, y si se define como funcin habr que acceder con 
QSelf().

Las  funciones  GET  y  SET  pueden corresponder a la misma funcin fsica, 
observando que en las  funciones  SET  se  las  va  a  pasar  un  valor  de 
asignacin como parmetro y en las funciones GET no  se  pasan  parmetros. 
Adems,  cuando  se  declara  una  variable  como lgica, sta no tiene una 
correspondencia  con  ninguna  fsica,  con  lo  que  el  programador  est 
obligado, en estos casos, a definir los mtodos SET y GET  para  establecer 
la relacin que l haya querido tener en cuenta.

A  continuacin  paso a describir brevemente la sintaxis para definicin de 
clases  con el programa que se acompaa OOPCLIP.PRG. En todos los programas 
que vayamos a utilizar estas opciones, hay que incluir la lnea:

# include "oopclip.ch"

Cuyo contenido es:

// Definicin comado CLASS
#xcommand CLASS <name> FROM <parent1> [, <parentN>] => ;
        CLASS <name> _FROM <parent1>() [, <parentN>()]

// Definicin comando CLASS redefinido para gestin de padres
#xcommand CLASS <name> [ _FROM <parent1> [,<parentN>] ] => ;
          FUNCTION <name> ;;
            STATIC hClass := 0 ;;
            LOCAL oParent, oNew, lProtecVar := .t., lProtecMet := .t. ;;
            if hClass == 0 ;;
              oParent:=__ClassDef( <"name">,{ <{parent1}> [,<{parentN}>]})

// Variables pblicas
#xcommand VAR PUBLIC => lProtecVar := .t.

// Variables protegidas
#xcommand VAR PRIVATE => lProtecVar := .f.

// Variables protegidas
#xcommand PROTECTED => lProtecVar := .f.

// Variables protegidas
#xcommand PROTECTED VAR => lProtecMet := .f.

// Mtodos pblicos
#xcommand METHOD PUBLIC => lProtecMet := .t.

// Mtodos protegidos
#xcommand METHOD PRIVATE => lProtecMet := .f.

// Mtodos protegidos
#xcommand PROTECTED METHOD => lProtecMet := .f.

// Variable nica pblica
#xcommand VAR <var1> [,<varn>] => ;
  __VarNew( <"var1">, lProtecVar ) [; __VarNew( <"varn">, lProtecVar )]

// Definicin de variables con funcin SET y funcin GET
#xcommand VAR <var> [SET <funset>] [GET <funget>] [<nodef: LOGICAL>]=> ;
      __VarNew( <"var">, lProtecVar, <"funset">, <"funget">, <.nodef.> )

// Definicin de variables con funcin GET y funcin SET
#xcommand VAR <var> [GET <funget>] [SET <funset>] [<nodef: OFF>]=> ;
      __VarNew( <"var">, lProtecVar, <"funset">, <"funget">, <.nodef.> )

// Descripcin de mtodos con formato MESSAGE
#xcommand MESSAGE <methodname> METHOD <methodudf> => ;
                __MethodNew( <"methodname">, <"methodudf">, lProtecMet )

// Descripcin de mtodos con formato MESSAGE sin METHOD
#xcommand MESSAGE <method1> [,<methodn>] => ;
                   __MethodNew( <"method1">, <"method1">, lProtecMet ) ;
                 [; __MethodNew( <"method1">, <"method1">, lProtecMet )]

// Comando ENDCLASS
#xcommand ENDCLASS => ;
                        hClass := __ClassMak() ;;
                end ;;
                oNew := __ClassIns(hClass) ;;
        RETURN oNew

// Definicin de mtodos funciones
#xcommand METHOD [FUNCTION] <*formato*> => ;
        FUNCTION <formato> ;;
                LOCAL Self := QSelf() ;;
                LOCAL lProtec := __ClassPro( Self, <"formato"> )
// Quitar la ltima linea si no se quiere dar proteccin a los mtodos

// Comando PARENT
#xtranslate Parent:<method>([<parameters,...>]) => ;
            Parent( <"method">, [<parameters>] )

#xtranslate END CLASS => ENDCLASS

#xtranslate :: => Self:

#xtranslate VARS => VAR

#xtranslate METHODS => METHOD


Descripcin de sintaxis de la construccin de clases

CLASS <name> FROM <parent1> [, <parentN>]

VAR PUBLIC  PRIVATE

PROTECTED [VAR]
VAR <var> [set <funset>] [GET <funget>] [LOGICAL>]

METHOD PUBLIC  PRIVATE
PROTECTED METHOD

MESSAGE <methodname> METHOD <methodudf>

ENDCLASS (o bien END CLASS)

// Descripcin para definir mtodos.

METHOD [FUNCTION] <namemethod>

// Descripcin para utilizar mtodos padre.

parent:<method>(<parameters,...>])

NOTA: Aunque admita herencia mltiple yo no aconsejo que se utilice  porque 
CLIPPER 5.2 no va a admitirla y es probable que no lo haga ni la versin 5.
X.


EJEMPLO DE UTILIZACION

Hasta  ahora  no hemos visto ninguna posibilidad real al tema, pero de esta 
forma  podemos,  por ejemplo, definir una clase que sirva como redefinicin 
de la clase TBrowse y as poder hacerla heredable, cosa tan echada de menos 
en CLIPPER 5.01,  de  esta  forma  podemos  adelantarnos  a  la  5.2  e  ir 
construyendo nuestros trabajos heredando estas clases y tenerlos dispuestos 
para cuando salga.

La definicin puede ser como sigue:

// Descripcin de la definicin del objeto TBrowse para hacerlo heredable:

CLASS TBN.

// Objeto TBrowse que se redefine

VAR oTBrowse

// Variables de la CLASS TBrowse estndar
VAR autoLite SET SGAuto GET SGAuto LOGICAL
VAR cargo SET SGCargo GET SGCargo LOGICAL
...

// Mtodos de la CLASS TBrowse estndar
MESSAGE addcolumn METHOD TBNaddColum
MESSAGE colorRect METHOD TBNcolorRect
...

ENDCLASS

En realidad estamos creando un objeto que va a tener una sola  variable  de 
instancia que, a su vez, va a ser un objeto TBrowse propio de Clipper.  Las 
dems variables de instancia son todas lgicas en las  que  se  definen  la 
forma de acceder a dicho objeto comn,  como  por  ejemplo  podra  ser  la 
funcin de crear la variable auto:

FUNCTION SGAuto( xVal )
RETURN 1f(xVal==nil, QSelf()[1]:auto, QSelf()[1] := xVal )

Podemos   observar  que  QSelf()  en  realidad  devuelve  un  objeto,  pero 
aprovechando la caracterstica de array de los objetos, accedemos a  la  1 
variable que no es ms que el objeto TBrowse de Clipper.

Ya slo nos quedan por definir los mtodos propios del  objeto  hacindolos 
de la misma forma que las funciones GET, SET por ejemplo:

METHOD TBNaddColumn( oColumn )
 ::oTBrowse:addColumn( oColumn )
RETURN self

En  el  programa  DEF_TBN.PRG  se  encuentra  el  fuente  completo  a  esta 
descripcin de clase TBrowse, para hacerla heredable lo nico  que  tenemos 
que  modificar en nuestros programas es la parte donde tengamos definidos 
los objetos con:

obj := TBrowse(.....)

Sustituir por:

obj := TBNNew(.....)

El contenido de dicho programa es:

///////////////////////////////////////////////////////////////////////
//
// Programa: DEF_TBN
//
///////////////////////////////////////////////////////////////////////
//
// Programa de redefinicin de la clase TBrowse para hacerla heredable
//
///////////////////////////////////////////////////////////////////////
//
// Autor: Antonio J. Rojo Lpez
//
//
//////////////////////////////////////////////////////////////////////

#include "inkey.ch"
#include "oopclip.ch"

#define    _OBJ    QSelf()[1]


CLASS TBN

        // Objeto TBrowse que se redefine
        VAR oTBrowse

        // Variables de la CLASS TBrowse estandar
        VAR autoLite SET SGAuto GET SGAuto LOGICAL
        VAR cargo SET SGCargo GET SGCargo LOGICAL
        VAR colCount SET SGColCount GET SGColCount LOGICAL
        VAR colorSpec SET SGColorSpec GET SGColorSpec LOGICAL
        VAR colPos SET SGColPos GET SGColPos LOGICAL
        VAR colSep SET SGColSep GET SGColSep LOGICAL
        VAR footSep SET SGFootSep GET SGFootSep LOGICAL
        VAR freeze SET SGFreeze GET SGFreeze LOGICAL
        VAR goBottomBlock SET SGGoBottom GET SGGoBottom LOGICAL
        VAR goTopBlock SET SGGoTop GET SGGoTop LOGICAL
        VAR headSep SET SGHeadSep GET SGHeadSep LOGICAL
        VAR hitBottom SET SGHitBottom GET SGHitBottom LOGICAL
        VAR hitTop SET SGHitTop GET SGHitTop LOGICAL
        VAR nBottom SET SGnBottom GET SGnBottom LOGICAL
        VAR nLeft SET SGnLeft GET SGnLeft LOGICAL
        VAR nRight SET SGnRight GET SGnRight LOGICAL
        VAR nTop SET SGnTop GET SGnTop LOGICAL
        VAR rowCount SET SGRowCount GET SGRowCount LOGICAL
        VAR rowPos SET SGRowPos GET SGRowPos LOGICAL
        VAR skipBlock SET SGSkipBlock GET SGSkipBlock LOGICAL
        VAR stable SET SGStable GET SGStable LOGICAL

        // Metodos de la CLASS TBrowse estandar 
        MESSAGE addColumn METHOD TBNaddColumn
        MESSAGE colorRect METHOD TBNcolorRect
        MESSAGE configure METHOD TBNconfigure
        MESSAGE deHilite METHOD TBNdeHilite
        MESSAGE down METHOD TBNdown
        MESSAGE end METHOD TBNend
        MESSAGE getColumn METHOD TBNgetColumn
        MESSAGE goBottom METHOD TBNgoBottom
        MESSAGE goTop METHOD TBNgoTop
        MESSAGE hilite  METHOD TBNhilite
        MESSAGE home METHOD TBNhome
        MESSAGE left METHOD TBNleft
        MESSAGE pageDown METHOD TBNpageDown
        MESSAGE pageUp METHOD TBNpageUp
        MESSAGE panEnd METHOD TBNpanEnd
        MESSAGE panHome METHOD TBNpanHome
        MESSAGE panLeft METHOD TBNpanLeft
        MESSAGE panRight METHOD TBNpanRight
        MESSAGE refreshAll METHOD TBNrfsAll
        MESSAGE refreshCurrent METHOD TBNrfsCurrent
        MESSAGE right METHOD TBNright
        MESSAGE setColumn METHOD TBNsetColumn
        MESSAGE stabilize METHOD stabilize
        MESSAGE up METHOD TBNup

        // Mtodos constructores
        MESSAGE new METHOD TBNnew
        MESSAGE db METHOD TBNdb

ENDCLASS


// Definicin de las asignaciones de variables
FUNCTION SGAuto( xVal )
RETURN if( xVal == nil, _OBJ:autolite, _OBJ:autolite := xVal )

FUNCTION SGCargo( xVal )
RETURN if( xVal == nil, _OBJ:cargo, _OBJ:cargo := xVal )

FUNCTION SGColCount( xVal )
RETURN if( xVal == nil, _OBJ:colCount, _OBJ:colCount := xVal )

FUNCTION SGColorSpec( xVal )
RETURN if( xVal == nil, _OBJ:colorSpec, _OBJ:colorSpec := xVal )

FUNCTION SGColPos( xVal )
RETURN if( xVal == nil, _OBJ:colPos, _OBJ:colPos := xVal )

FUNCTION SGColSep( xVal )
RETURN if( xVal == nil, _OBJ:colSep, _OBJ:colSep := xVal )

FUNCTION SGFootSep( xVal )
RETURN if( xVal == nil, _OBJ:footSep, _OBJ:footSep := xVal )

FUNCTION SGFreeze( xVal )
RETURN if( xVal == nil, _OBJ:freeze, _OBJ:freeze := xVal )

FUNCTION SGGoBottom( xVal )
RETURN if( xVal == nil, _OBJ:goBottomBlock, _OBJ:goBottomBlock := xVal )

FUNCTION SGGoTop( xVal )
RETURN if( xVal == nil, _OBJ:goTopBlock, _OBJ:goTopBlock := xVal )

FUNCTION SGHeapSep( xVal )
RETURN if( xVal == nil, _OBJ:heapSep, _OBJ:heapSep := xVal )

FUNCTION SGHitBottom( xVal )
RETURN if( xVal == nil, _OBJ:hitBottom, _OBJ:hitBottom := xVal )

FUNCTION SGHitTop( xVal )
RETURN if( xVal == nil, _OBJ:hitTop, _OBJ:hitTop := xVal )

FUNCTION SGnBottom( xVal )
RETURN if( xVal == nil, _OBJ:nBottom, _OBJ:nBottom := xVal )

FUNCTION SGnLeft( xVal )
RETURN if( xVal == nil, _OBJ:nLeft, _OBJ:nLeft := xVal )

FUNCTION SGnRight( xVal )
RETURN if( xVal == nil, _OBJ:nRight, _OBJ:nRight := xVal )

FUNCTION SGnTop( xVal )
RETURN if( xVal == nil, _OBJ:nTop, _OBJ:nTop := xVal )

FUNCTION SGRowCount( xVal )
RETURN if( xVal == nil, _OBJ:rowCount, _OBJ:rowCount := xVal )

FUNCTION SGRowPos( xVal )
RETURN if( xVal == nil, _OBJ:rowPos, _OBJ:rowPos := xVal )

FUNCTION SGSkipBlock( xVal )
RETURN if( xVal == nil, _OBJ:SkipBlock, _OBJ:SkipBlock := xVal )

FUNCTION SGStable( xVal )
RETURN if( xVal == nil, _OBJ:stable, _OBJ:stable := xVal )


// Definicin de los mtodos

METHOD FUNCTION TBNaddColumn( oColumn )
        ::oTBrowse:addColumn( oColumn )
RETURN self

METHOD FUNCTION TBNcolorRect( aRect, aColores )
        ::oTBrowse:colorRect( aRect, aColores )
RETURN self

METHOD FUNCTION TBNconfigure()
        ::oTBrowse:configure()
RETURN self

METHOD FUNCTION TBNdeHilite()
        ::oTBrowse:deHilite()
RETURN self

METHOD FUNCTION TBNdown()
        ::oTBrowse:down()
RETURN self

METHOD FUNCTION TBNend()
        ::oTBrowse:end()
RETURN self

METHOD FUNCTION TBNgetColumn( nColumn )
RETURN ::oTBrowse:getColumn( nColumn )

METHOD FUNCTION TBNgoBottom()
        ::oTBrowse:goBottom()
RETURN self

METHOD FUNCTION TBNgoTop()
        ::oTBrowse:goTop()
RETURN self

METHOD FUNCTION TBNhilite()
        ::oTBrowse:hilite()
RETURN self

METHOD FUNCTION TBNhome()
        ::oTBrowse:home()
RETURN self

METHOD FUNCTION TBNleft()
        ::oTBrowse:left()
RETURN self

METHOD FUNCTION TBNpageDown()
        ::oTBrowse:pageDown()
RETURN self

METHOD FUNCTION TBNpageUp()
        ::oTBrowse:pageUp()
RETURN self

METHOD FUNCTION TBNpanEnd()
        ::oTBrowse:panEnd()
RETURN self

METHOD FUNCTION TBNpanHome()
        ::oTBrowse:panHome()
RETURN self

METHOD FUNCTION TBNpanLeft()
        ::oTBrowse:panLeft()
RETURN self

METHOD FUNCTION TBNpanRight()
        ::oTBrowse:panRight()
RETURN self

METHOD FUNCTION TBNrfsAll()
        ::oTBrowse:refreshAll()
RETURN self

METHOD FUNCTION TBNrfsCurrent()
        ::oTBrowse:refreshCurrent()
RETURN self

METHOD FUNCTION TBNright()
        ::oTBrowse:right()
RETURN self

METHOD FUNCTION TBNsetColumn( nColumn, oColumn )
RETURN ::oTBrowse:setColumn( nColumn, oColumn )

METHOD FUNCTION stabilize()
RETURN ::oTBrowse:stabilize()

METHOD FUNCTION TBNup()
        ::oTBrowse:up()
RETURN self

FUNCTION TBNnew( nUp, nLeft, nDown, nRight )
        LOCAL oTBN := TBN()
        oTBN:oTBrowse := TBrowseNew( nUp, nLeft, nDown, nRight )
RETURN oTBN

FUNCTION TBNdb( nUp, nLeft, nDown, nRight )
        LOCAL oTBN := TBN()
        oTBN:oTBrowse := TBrowseDB( nUp, nLeft, nDown, nRight )
RETURN oTBN

Y  lo  mismo  con  su  homnima TBrowseDB(....) por TBNDB(....), con lo que 
nuestros programas utilizan ahora una clase que es heredable.

En una prxima ocasin podremos aprovechar esta forma  de  definir  objetos 
para   agilizarlos  y  verificar  datos, sin  coste  en  ejecucin  de  las 
aplicaciones  y  aprovechando  la  OOP,  sin ese miedo a la lentitud de los 
programas, aunque siempre sern ms lentos que las funciones.

El programa OOPCLIP.PRG ser:

/* --------------------------------------------------------------------- */
/*                                                                       */
/*   Funciones para manejo de clases                                     */
/*                                                                       */
/*                                                                       */
/* --------------------------------------------------------------------- */
#include "oopclip.ch"
#include "fivepro.ch"

// Variables para gestin de la configuracin de objetos
STATIC aListClass := {}                 // Nombres de las clases
STATIC aListMethod := {}                // Nombres de los mtodos
STATIC aListVar := {}                   // Nombres de las variables
STATIC aListParent := {}                // Nombres de las clases padres
STATIC nCourrent := 0                   // Nmero de clase actual

// Funcin para definicin inicial de clase
FUNCTION __ClassDef( cName, bParent )

        LOCAL nParent, oParent := nil, nTot, nVar, nMet

        AAdd( aListClass, '' )    // Aade el nombre de la clase a la lista
        AAdd( aListVar, {} )      // Aade lista vacia de variables
        AAdd( aListMethod, {} )   // Aade lista vacia de mtodos
        AAdd( aListParent, {} )   // Aade lista vacia de padres

        // Se crea la clase padre si no estuviera creada
        if ! Empty( bParent )
                for nTot := 1 to Len( bParent )
                        Eval( bParent[ nTot ] )
                next
        endif

        // Suma 1 al objeto actual
        ++nCourrent

        // Aade el nombre de la clase a la lista
        aListClass[nCourrent] := Upper( cName )

        // Procesa si hay padres definidos
        if ! Empty( bParent )

           // Aade el mtodo definido como padre
           __MethodNew( 'Parent', '__Parent', .t. )

           for nTot := 1 to Len( bParent )
// Se asigna un objeto de la clase padre a la actual para herencia
                oParent := Eval( bParent[ nTot ] )
                AAdd( aListParent[nCourrent], oParent )

// Aade las variables y los mtodos de los padres a las matrices
                nParent := AScan(aListClass,oParent:ClassName())
                if nParent <> 0
                   for nMet := 1 to Len( aListMethod[ nParent ] )
                     __MethodNew(aListMethod[nParent,nMet,1],;
                                 aListMethod[nParent,nMet,2],;
                                 aListMethod[nParent,nMet,3])
                   next
                   for nVar := 1 to Len( aListVar[ nParent ] )
                     __VarNew( aListVar[nParent,nVar,1],;
                               aListVar[nParent,nVar,2],;
                               aListVar[nParent,nVar,3],;
                               aListVar[nParent,nVar,4],;
                               aListVar[nParent,nVar,5] )
                   next
                 endif

          next
        endif

RETURN nil


// Funcin para definicin de variables en una clase
FUNCTION __VarNew( cName, lProtected, cFunSet, cFunGet, lDefVar )

        LOCAL n

        DEFAULT cFunSet := ''
        DEFAULT cFunGet := ''
        DEFAULT lDefVar := .f.

        cName := Upper( cName )
        n := AScan( aListVar[ nCourrent ], ;
                    { |aVar| aVar[1] == cName } )
        if n > 0
          aListVar[ nCourrent, n ] := { cName, ;
                                        lProtected, ;
                                        cFunSet, ;
                                        cFunGet, ;
                                        lDefVar }
        else
          AAdd( aListVar[ nCourrent ], ;
                { cName, lProtected, cFunSet, cFunGet, lDefVar } )
        endif

RETURN nil




// Funcion para definicin de metodos en una clase
FUNCTION __MethodNew( cName, cUDF, lProtected )

        LOCAL n

        cName := Upper( if( At('(',cName)>0, ;
                            SubStr( cName, 1, At('(',cName)-1 ), ;
                            cName ) )
        cUDF := Upper( if( At('(',cUDF)>0, ;
                           SubStr( cUDF, 1, At('(',cUDF)-1 ), ;
                           cUDF ) )
        n := AScan( aListMethod[ nCourrent ], ;
                    { |aMethod| aMethod[1] == cName } )
        if n > 0
           aListMethod[ nCourrent, n ] := { cName , cUDF, lProtected }
        else
           AAdd(aListMethod[ nCourrent ], { cName , cUDF, lProtected } )
        endif

RETURN nil




// Funcin de creacin final de la clase despues de haberse definido
FUNCTION __ClassMak()

        LOCAL nHandle, f , cVar, s, nDecVar := 0, nContVar := 0

        for f := 1 to Len( aListVar[ nCourrent ] )
                if ! aListVar[nCourrent,f,5]
                        nDecVar++
                endif
        next

        nHandle := __ClassNew( aListClass[ nCourrent ], nDecVar )

        for f := 1 to Len( aListVar[ nCourrent ] )
                nContVar += If( aListVar[nCourrent,f,5], 0, 1 )
                cVar := aListVar[ nCourrent , f, 1 ]
                s := if( nContVar<10, ;
                         '0'+Str(nContVar,1), ;
                         Str(nContVar,2) )

                // Variables get
                do case
                case Len(aListVar[ nCourrent, f, 4 ]) <> 0
                    __ClassAdd( nHandle, ;
                                cVar, ;
                                SubStr(aListVar[nCourrent,f,4],1,10) )
                otherwise
                    __ClassAdd( nHandle, cVar, '__oSet'+s)
                endcase

                // Variables set
                do case
                case Len(aListVar[ nCourrent, f, 3 ]) <> 0
                   __ClassAdd( nHandle, '_'+cVar, ;
                               SubStr(aListVar[nCourrent,f,3],1,10) )
                case ! aListVar[ nCourrent, f, 2 ]
                   __ClassAdd( nHandle, '_'+cVar, '__oPro'+s)
                otherwise
                        __ClassAdd( nHandle, '_'+cVar, '__oSet'+s)
                endcase
        next

        // Mtodos
        AEval( aListMethod[ nCourrent ], ;
               { |cMethod| __ClassAdd( nHandle, ;
                                       cMethod[ 1 ], ;
                                       cMethod[ 2 ] ) } )

RETURN nHandle

// Funcin de control de proteccin de mtodos
FUNCTION __ClassPro( oSelf , cNomMethod )

        LOCAL nPosClass, cNomClass, cNomVar, nMet
        
        cNomClass := oSelf:ClassName()
        nPosClass := AScan( aListClass, cNomClass )
        cNomMethod := Upper( if(At('(',cNomMethod)>0,;
                                SubStr(cNomMethod,1,;
                                At('(',cNomMethod)-1),cNomMethod) )
        nMet := AScan( aListMethod[nPosClass], ;
                { |aMethod| Upper(aMethod[2]) == cNomMethod } )

        if ! aListMethod[ nPosClass, nMet, 3 ]
           cNomVar := SubStr(ProcName(2),5)
           if AScan( aListMethod[nPosClass], ;
                     { | aNomMethod | aNomMethod[1] == cNomVar } ) = 0
              __EjeErr( oSelf, 'Method protected')
           endif
        endif 

RETURN .t.


// Funciones generales gestion de variables de la clase tanto set como get
FUNCTION __oSet01( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[01],QSelf()[01]:=xVal)

FUNCTION __oSet02( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[02],QSelf()[02]:=xVal)

FUNCTION __oSet03( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[03],QSelf()[03]:=xVal)

FUNCTION __oSet04( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[04],QSelf()[04]:=xVal)

FUNCTION __oSet05( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[05],QSelf()[05]:=xVal)

FUNCTION __oSet06( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[06],QSelf()[06]:=xVal)

FUNCTION __oSet07( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[07],QSelf()[07]:=xVal)

FUNCTION __oSet08( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[08],QSelf()[08]:=xVal)

FUNCTION __oSet09( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[09],QSelf()[09]:=xVal)

FUNCTION __oSet00( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[10],QSelf()[10]:=xVal)

FUNCTION __oSet11( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[11],QSelf()[11]:=xVal)

FUNCTION __oSet12( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[12],QSelf()[12]:=xVal)

FUNCTION __oSet13( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[13],QSelf()[13]:=xVal)

FUNCTION __oSet14( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[14],QSelf()[14]:=xVal)

FUNCTION __oSet15( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[15],QSelf()[15]:=xVal)

FUNCTION __oSet16( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[16],QSelf()[16]:=xVal)

FUNCTION __oSet17( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[17],QSelf()[17]:=xVal)

FUNCTION __oSet18( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[18],QSelf()[18]:=xVal)

FUNCTION __oSet19( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[19],QSelf()[19]:=xVal)

FUNCTION __oSet20( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[20],QSelf()[20]:=xVal)

FUNCTION __oSet21( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[21],QSelf()[21]:=xVal)

FUNCTION __oSet22( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[22],QSelf()[22]:=xVal)

FUNCTION __oSet23( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[23],QSelf()[23]:=xVal)

FUNCTION __oSet24( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[24],QSelf()[24]:=xVal)

FUNCTION __oSet25( xVal )
RETURN If(xVal==nil,xVal:=QSelf()[25],QSelf()[25]:=xVal)

FUNCTION __oSet26( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[26],QSelf()[26]:=xVal)

FUNCTION __oSet27( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[22],QSelf()[27]:=xVal)

FUNCTION __oSet28( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[28],QSelf()[28]:=xVal)

FUNCTION __oSet29( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[29],QSelf()[29]:=xVal)

FUNCTION __oSet30( xVal ) 
RETURN If(xVal==nil,xVal:=QSelf()[30],QSelf()[30]:=xVal)

// Funcines generales gestion de variables protegidas de la clase
FUNCTION __oPro01( xVal ) ; RETURN __ProtecVar( QSelf(), 01, xVal )
FUNCTION __oPro02( xVal ) ; RETURN __ProtecVar( QSelf(), 02, xVal )
FUNCTION __oPro03( xVal ) ; RETURN __ProtecVar( QSelf(), 03, xVal )
FUNCTION __oPro04( xVal ) ; RETURN __ProtecVar( QSelf(), 04, xVal )
FUNCTION __oPro05( xVal ) ; RETURN __ProtecVar( QSelf(), 05, xVal )
FUNCTION __oPro06( xVal ) ; RETURN __ProtecVar( QSelf(), 06, xVal )
FUNCTION __oPro07( xVal ) ; RETURN __ProtecVar( QSelf(), 07, xVal )
FUNCTION __oPro08( xVal ) ; RETURN __ProtecVar( QSelf(), 08, xVal )
FUNCTION __oPro09( xVal ) ; RETURN __ProtecVar( QSelf(), 09, xVal )
FUNCTION __oPro10( xVal ) ; RETURN __ProtecVar( QSelf(), 10, xVal )
FUNCTION __oPro11( xVal ) ; RETURN __ProtecVar( QSelf(), 11, xVal )
FUNCTION __oPro12( xVal ) ; RETURN __ProtecVar( QSelf(), 12, xVal )
FUNCTION __oPro13( xVal ) ; RETURN __ProtecVar( QSelf(), 13, xVal )
FUNCTION __oPro14( xVal ) ; RETURN __ProtecVar( QSelf(), 14, xVal )
FUNCTION __oPro15( xVal ) ; RETURN __ProtecVar( QSelf(), 15, xVal )
FUNCTION __oPro16( xVal ) ; RETURN __ProtecVar( QSelf(), 16, xVal )
FUNCTION __oPro17( xVal ) ; RETURN __ProtecVar( QSelf(), 17, xVal )
FUNCTION __oPro18( xVal ) ; RETURN __ProtecVar( QSelf(), 18, xVal )
FUNCTION __oPro19( xVal ) ; RETURN __ProtecVar( QSelf(), 19, xVal )
FUNCTION __oPro20( xVal ) ; RETURN __ProtecVar( QSelf(), 20, xVal )
FUNCTION __oPro21( xVal ) ; RETURN __ProtecVar( QSelf(), 21, xVal )
FUNCTION __oPro22( xVal ) ; RETURN __ProtecVar( QSelf(), 22, xVal )
FUNCTION __oPro23( xVal ) ; RETURN __ProtecVar( QSelf(), 23, xVal )
FUNCTION __oPro24( xVal ) ; RETURN __ProtecVar( QSelf(), 24, xVal )
FUNCTION __oPro25( xVal ) ; RETURN __ProtecVar( QSelf(), 25, xVal )
FUNCTION __oPro26( xVal ) ; RETURN __ProtecVar( QSelf(), 26, xVal )
FUNCTION __oPro27( xVal ) ; RETURN __ProtecVar( QSelf(), 27, xVal )
FUNCTION __oPro28( xVal ) ; RETURN __ProtecVar( QSelf(), 28, xVal )
FUNCTION __oPro29( xVal ) ; RETURN __ProtecVar( QSelf(), 29, xVal )
FUNCTION __oPro30( xVal ) ; RETURN __ProtecVar( QSelf(), 30, xVal )

// Funcin general para gestin de variables protegidas
FUNCTION __ProtecVar( oSelf, nVar, xVal )

        LOCAL nPosClass, cNomClass, cNomVar, e, eb

        cNomClass := oSelf:ClassName()
        nPosClass := AScan( aListClass, cNomClass )
        cNomVar := SubStr(ProcName(2),5)
        if AScan( aListMethod[nPosClass], ;
                  { | aNomMethod | aNomMethod[1] == cNomVar } ) > 0
                if xVal == nil
                        xVal := oSelf[ nVar ]
                else
                        oSelf[ nVar ] := xVal
                endif
        else
                __EjeErr( oSelf, 'Variable protected')
        endif

RETURN xVal


// Funcin de ejecucin de error en mtodos y variables protegidos
FUNCTION __EjeErr( oSelf, cOperation )

        LOCAL e, eb

        e := ErrorNew()
        e:Args := { oSelf, 0 }
        e:canDefault := .f.
        e:canRetry := .f.
        e:canSubstitute := .t.
        e:description := 'No exported variable: '
        e:fileName := ""
        e:genCode := 16
        e:operation := cOperation
        e:osCode := 0
        e:severity := 2
        e:subCode := '1005'
        e:subSystem := 'BASE'
        e:tries := 0
        eb := ErrorBlock()
        Eval(eb,e)

RETURN nil


// Mtodo para gestin de los mtodos padres
METHOD __Parent( cNameMethod, xPar0, xPar1, xPar2, ;
                 xPar3, xPar4, xPar5, xPar6, xPar7, xPar8, xPar9 )

        LOCAL xReturn, nClass := AScan( aListClass,::ClassName() )
        LOCAL nParent, f, nPosVar, cVar, cNameParent, nPos, ;
              nClasParent, nConMet

        PRIVATE cEject, oParent, xP0, xP1, xP2, xP3,;
                xP4, xP5, xP6, xP7, xP8, xP9

        if (nPos := At(':',cNameMethod))>0
                cNameParent := Upper(SubStr(cNameMethod,1,nPos-1))
                cNameMethod := SubStr(cNameMethod,nPos+1)
        else
                cNameParent := aListParent[nClass,1]:ClassName()
        endif

        nClassParent := AScan(aListClass,Upper(cNameParent))
        nParent := AScan(aListParent[nClass],;
                         {|o| o:ClassName()=Upper(cNameParent)})
        oParent := aListParent[nClass,nParent]
        nConMet := 0
        for f := 1 to Len(aListVar[nClassParent])
           if ! AlistVar[nClassParent,nConMet+1,5]
               nConMet++
               cVar := AlistVar[nClassParent,nConMet,1]
               nPosVar := AScan(AlistVar[nClass],{|x| x[1]=cVar})
               oParent[nConMet] := Self[nPosVar]
           endif
        next
        cEject := 'oParent:'+cNameMethod+'('
        xP0 := xPar0
        xP1 := xPar1
        xP2 := xPar2
        xP3 := xPar3
        xP4 := xPar4
        xP5 := xPar5
        xP6 := xPar6
        xP7 := xPar7
        xP8 := xPar8
        xP9 := xPar9
        cEject += If(ValType(xPar0)<>'U','xP0','')
        cEject += If(ValType(xPar1)<>'U',',xP1','')
        cEject += If(ValType(xPar2)<>'U',',xP2','')
        cEject += If(ValType(xPar3)<>'U',',xP3','')
        cEject += If(ValType(xPar4)<>'U',',xP4','')
        cEject += If(ValType(xPar5)<>'U',',xP5','')
        cEject += If(ValType(xPar6)<>'U',',xP6','')
        cEject += If(ValType(xPar7)<>'U',',xP7','')
        cEject += If(ValType(xPar8)<>'U',',xP8','')
        cEject += If(ValType(xPar9)<>'U',',xP9','')
        cEject += ')'
        xReturn := &cEject
        nConMet := 0
        for f := 1 to Len(aListVar[nClassParent])
           if ! AlistVar[nClassParent,nConMet+1,5]
              nConMet++
              cVar := AlistVar[nClassParent,nConMet,1]
              nPosVar := AScan(AlistVar[nClass],{|x| x[1]=cVar})
              Self[nPosVar] := oParent[nConMet]
           endif
        next

RETURN xReturn

**************************************************************************
**************************************************************************
"Pseudo qu ...?" Diris algunos. "Vaya, hombre, como no tenamos bastante 
con la abuela, la OOP pari mellizos", dirn otros.
 
Y no les falta razn a unos y a otros, pero de todo tiene que haber  en  la 
via  del Seor..., en fin, qu le vamos a hacer, a todos los tontos nos da 
por algo, y a m, hace ya tiempo que me dio por este engendro que he tenido 
a bien en bautizar (a falta de un termino conocido que los defina) como eso 
que aparece en el ttulo de ste artculo.
 
Un poco de historia para empezar. Corra el ao de gracia de nuestro  Seor 
Jesucristo de 1990 (casi perdido ya en los anales de los CD-ROM), Nantucket 
haba tenido a bien en sacar la versin 5.0 de Clipper, la cual inclua OOP 
-Object  Oriented  Programming- (al menos, esas eran las tesis oficiales) y 
uno,  pobre  infeliz,  andaba pelendose con el C++, a ver qu puetas eran 
eso  de  los objetos, haciendo conjeturas y lindose en discusiones con los 
amigos  -Antonio Linares y Anto Gabarrn principalmente- sobre qu sera un 
objeto,  una  clase,  la  herencia  y  sobre  todo  aquello  tan  raro  del 
polimorfismo. Preguntndonos sobre todo, en qu nos  facilitara  la  tarea 
del programar diario toda aquella parafernalia y si el  esfuerzo  necesario 
para comprender todo aquello, se vera alguna vez recompensado.
 
Con  ms  dudas que medios nos pusimos a ello Antonio y yo, animados con la 
nueva versin de Clipper y centrndonos ms en la OOP que en los arrays  de 
punteros a funciones y las listas doblemente enlazadas propias del C y que, 
al usar este lenguaje, nos restaban atencin del fin primordial.  Aparcamos 
pues el C++ y nos metimos con Clipper 5.0.
 
La  desilusin  fue  demoledora cuando descubrimos que ni OOP ni puetas. 5 
hermosas  clases 5, que podan ser lidiadas con el permiso del compilador y 
si  el  VMM  lo permita (hecho poco frecuente); pero de herencia nada y no 
hablemos de generar clases nuevas, all de eso no apareca nada, y mira que 
le dimos vueltas a los manuales.
 
Antonio,  mucho ms tozudo que yo, se empe en que habra que sacar clases 
de  donde  no  las  haba. Leyendo un da un ejemplar del DataBase Advisor, 
encontr  un  artculo  de  un francs que montaba un OOPS -Object Oriented
Programming System- en Clipper basndose en el preprocesador y  los  arrays 
multidimensionales. Sistema parco, s, pero casi OOP,  oiga.  de  nuevo  la 
adrenalina  a  flor  de  piel  y  a  dilucidar  cmo mejorar el sistema del 
francs,  cosa  no  demasiado  difcil  ahora,  pero  en aquellos entonces, 
tenamos que empezar por aprender a manejar el  preprocesador  de  Clipper, 
que dista bastante del de C.
 
Montamos  una estructura de datos bastante compleja, pero el resultado, aun 
cuando mejoraba el del francs no nos satisfizo plenamente. Sin embargo, de 
aquello  sacamos  varias  cosas:  aprendimos  a  manejar  el preprocesador, 
estructuras  complejas  de  arrays  multidimensionales con soltura y, sobre 
todo, entendimos los intrngulis de la OOP.
 
Poco despus Anton Van Straten (a quien hay echarle de comer  aparte)  sac 
su Class(y) y al poco apareci oClip, Antonio sigui en sus trece de montar 
un OOPS y yo me dediqu a otros menesteres. Fruto de su trabajo son Objects 
y  Dialog,  hartos conocidos. Gran parte del fruto de mi trabajo se perdi, 
como me pasa siempre por mi inconstancia, en discusiones conmigo mismo,  un 
par de programas que siguen sin terminar en algn lugar recndito del disco 
duro (algunos en C++ y otros en Class(y)), y poco ms. Lo ms importante de 
aquello, fue el conocimiento que obtuve de la OOP, parte del cual acabo  de 
plasmar en una publicacin que aparecer prximamente.
 
Sin embargo, la semilla estaba plantada, hace ahora 6 meses, estaba leyendo 
un artculo en esta revista sobre SOS - un sistema de generacin de  ayudas 
para Clipper- cuando decid que aquello  era  evidentemente  mejorable.  Me 
puse manos a la obra, siendo el resultado un engendro que conoceris por el 
nombre  de  HelpEdit.  El problema que se me planteaba es que (debido a los 
vicios  adquiridos)  necesitaba  de  OOP  para montarlo, pero Clipper no lo 
tiene,  as, que tena que montarlo con estructuras de datos soportadas por 
lo nico que en Clipper las soporta, los arrays multidimensionales.
 
Con  teclado  en  ristre  y con casi tanto valor como imaginacin me puse a 
teclear.  HelpEdit,  soporta  tantos  sistemas  de  ayuda distintos como se 
desee, cada uno de ellos puede por lo tanto considerarse como un  objeto  -
una instanciacin de su clase en palabras de OOP- de la clase Help. Adems, 
cada  uno  es  totalmente  independiente  de  los  dems  y  autnomo en su 
funcionamiento.
 
Posteriormente, he realizado CuaWise, con tcnicas parecidas (este engendro 
aparecio en el ltimo nmero de ClippeRmana. A este respecto, lase nota a 
pi  de  artculo,  por  favor).  En  esta  ocasin,  me  aprovech  de las 
estructuras montadas en la clase GET de Clipper para no trabajar dos  veces 
y  ahorrar  memoria  (obsesin  constante  en mi persona: que todo ocupe lo 
mnimo  imprescindible,  y  adems  una  necesidad debido a la voracidad de 
memoria que atae a Clipper). En  este  trabajo,  no  cre  nuevas  pseudo-
clases, sino que realic pseudo-derivaciones de la clase GET. Este sistema, 
como  es  lgico  est  ya  algo  mejorado  y  pretendo  intentar mejorarlo 
ostensiblemente en un futuro cercano -si mi chica me lo permite y dejan  de 
servir alcohol en los bares, cosas ambas bastante poco probables-. An as, 
no s de nadie que haya montado un sistema similar anteriormente.
 
Vale, chaval, ya est bien de parrafadas y de historietas.  Amable  lector, 
lleva usted toda la razn, pero al que no le guste contar batallitas de vez 
en cuando, que tire el primer chip.
 
Vamos  al  grano. Lo importante es comprender qu es la OOP, lo dems se os 
dar por aadidura.
 
La OOP no es un lenguaje de programacin, ni produce programas de  un  tipo 
especial  (v.g.  interfaces de usuarios, en los que siempre se usa la OOP), 
sino  que  son  una  serie  de  tcnicas  de  programacin;  y  como tales, 
aplicables a cualquier lenguaje de programacin. Como es  lgico,  aquellos 
lenguajes  que  soportan  un  OOPS  completo  nos  facilitan la labor y nos 
permiten aplicar estas tcnicas en profundidad.
 
La  leccin  principal  de la OOP es la siguiente: se deben crear elementos 
autnomos -es decir, que no puedan  sufrir  efectos  colaterales  de  otros 
elementos-,  y solo se debe acceder a ellos mediante funciones (mensajes si 
lo prefers) que los manejen. Cuanto ms especficos sean estos elementos -
menor sea el campo de accin que cubren-  ms  probablemente  podrn  sern 
reutilizables por otros.
 
Un elemento (objeto) puede estar constituido por otros  muchos,  pero  cada 
uno debe ser autnomo, el macro-elemento, manejar a sus elementos a travs 
de mensajes, no directamente.
 
Esto es lo primordial para que nuestro cdigo resulte re-usable,  fcil  de 
mejorar y de mantener. Y para esto no hacen falta clases reales.  Para  que 
la re-usabilidad sea efectiva y no se quede a medias,  lo  que  nos  ahorra 
muchas horas de reprogramacin de lo que ya est  hecho,  es  necesaria  la 
herencia, sin embargo, esta  no  es  fcilmente  implementable,  cuando  el 
lenguaje no dispone de ella.
 
Hay  otra  leccin ms, primordial tambin, que no proviene directamente de 
la OOP, pero se deriva de ella. De ello hablaremos ms adelante.
 
Bien, ya sabemos que un objeto (de aqu en adelante omitiremos  el  prefijo 
"pseudo" para no amargarnos la vida) debe contener  en  s  mismo  toda  la 
informacin que necesita, estos son sus datos, los mtodos (funciones)  son 
externos  a  l, pero al aplicarle estos mtodos a los objetos, conseguimos 
que se comporten de uno u otro modo.
 
Vamos  ya  pues  con el primer ejemplo. Todos tenemos en nuestro directorio 
CLIPPER5\SOURCE\SAMPLES un programa llamado Status.prg, esa es la madre del 
cordero. Yo he reproducido el mismo ejemplo dndole un par de retoques para 
hacerlo un poquito ms OOP.
 
El  ejemplo se divide en dos partes: la primera y la segunda (es broma). La 
incluida en la funcin Main() que sirve para demostrar el funcionamiento de 
la clase Estado y segunda que es la codificacin de la clase.
 
Esta  clase solo dispone de dos mtodos EstadoNew() el constructor, que nos 
devuelve un objeto de perteneciente a su clase; y EstadoShow(), que trabaja 
con el objeto (el resultado de ese trabajo,  es  fcilmente  apreciable  en 
pantalla).
 
Es muy importante  que  observes  cmo  todos  los  datos  necesarios  para 
trabajar con el objeto se residen en el propio objeto.
 
Note  as  mismo,  cmo  el  uso del preprocesador (un uso precario en este 
caso)  nos permite ampliar o reducir la estructura del objeto sin tener que 
alterar  el  cdigo  que  ya  tenemos,  ya  que los ndices del array estn 
referenciados a travs de los #defines. As, si deseamos ampliar los  datos 
de  este  objeto,  no  tenemos que tocar nada de lo ya escrito, simplemente 
aadiremos  la  nueva  etiqueta  (#define)  para  el  ndice  del  array  y 
ampliaremos el nmero de la etiqueta de la longitud del array. Este modo de 
trabajo tambin nos ofrece otra ventaja, la facilidad de lectura del cdigo 
que  ello  supone,  no  perdiendo  en  velocidad de ejecucin, aunque s de 
escritura (ms se pierde luego al repasarlo y no entender nada).
 
Observa cmo est realizado, pero sobre todo, plantate lo que de innovador 
y beneficioso supone este planteamiento con respecto a los modos de trabajo 
tradicionales.
 
Bueno, piensa sobre ello, y si te  parece  interesante,  te  invito  a  una 
segunda parte en el prximo nmero.
 
 
================= ESTADO.PRG =============================== 
function main() 
   local aStsPru    := EstadoNew(),; 
         aaStsOtros := {},; 
         i 
   SetCursor( 0 ) 
   CLS 
 
   //----------- Un solo objeto --------------- 
   do while Inkey( .1 ) == 0 
      EstadoShow( aStsPru ) 
   enddo 
 
   //----------- Ahora muchos ---------------- 
   CLS 
 
   for i = 1 to 20 
       AAdd( aaStsOtros, EstadoNew( i, i * 4 - 2 ) ) 
   next 
 
   i = 1 
 
   do while Inkey() == 0 
      EstadoShow( aaStsOtros[ i ] ) 
      i = If( i+1 > 20, 1, i+1 ) 
   enddo 
   SetCursor( 1 ) 
return NIL 
 
/*------------------------------------------------------------------------ 
 La funcin EstadoNew() es la que podemos considerar como el "constructor" 
 de esta nueva "clase" de datos.
 Esta funcin es la que crea la  estructura  de  datos  e  inicializa  sus 
 valores  para  poder ser posteriormente tratados por la funcin ("mtodo" 
 en OOP) EstadoShow().
 El  preprocesador  lo  utilizamos tanto para clarificar los elementos del 
 array  como  para  facilitar  una  posible  posterior  ampliacin  de  la 
 estructura sin tener que modificar los elementos existentes.
------------------------------------------------------------------------*/ 
#define STS_FILA   1          // Fila donde se mostrar el objeto 
#define STS_COL    2          // Columna donde se mostrar el objeto 
#define STS_COLOR  3          // Color del objeto 
#define STS_ACTUAL 4          // Estado actual de presentacin 
#define STS_IMAGEN 5          // Array con cada una de las imagenes
                              // de un ciclo 
#define STS_LEN    5          // Longitud del array STATUS 
 
function EstadoNew( nFila, nCol, cColor ) 
 
   local aStat[ STS_LEN ] 
 
   aStat[ STS_FILA   ] = If( nFila  == nil, 0,      nFila ) 
   aStat[ STS_COL    ] = If( nCol   == nil, 0,      nCol ) 
   aStat[ STS_COLOR  ] = If( cColor == nil, "W+/N", cColor ) 
   aStat[ STS_ACTUAL ] = .T.
   aStat[ STS_IMAGEN ] = { "","" } 
 
return aStat 
 
/*------------------------------------------------------------------------ 
 Esta  funcin trabaja con la estructura de datos devuelta por el "pseudo- 
 constructor"  EstadoNew(),  actualizando  la  imagen  en  la  pantalla  y 
 almacenando en nuevo estado en el que se  encuentra  el  indicador  (como 
 este indicador es biestado, su estado interno,  lo  almacenamos  como  un 
  valor lgico, para de este modo acelerar los procesos).
------------------------------------------------------------------------*/ 
function EstadoShow( aStat ) 
 
   @ aStat[ STS_FILA ], aStat[ STS_COL ] SAY; 
     If( aStat[ STS_ACTUAL ], " " + aStat[ STS_IMAGEN ][ 1 ],; 
                              aStat[ STS_IMAGEN ][ 1 ] + " " ); 
                        COLOR aStat[ STS_COLOR ] 
 
   @ aStat[ STS_FILA ]+1, aStat[ STS_COL ] SAY; 
     If( aStat[ STS_ACTUAL ], aStat[ STS_IMAGEN ][ 2 ] + " ",; 
                              " " + aStat[ STS_IMAGEN ][ 2 ] ); 
                        COLOR aStat[ STS_COLOR ] 
 
   aStat[ STS_ACTUAL ] = !aStat[ STS_ACTUAL ] 
return NIL 
====================== EOF ========================== 
***************************************************************************
