Objetos en BBj

El presente tutorial introduce el concepto de objetos, que lleva el poder de la programación orientada a objetos al lenguaje BBj.

Introducción a las Clases en BBj

Desde la versión 6.0 de BBj, existe soporte para clases, el que permite escribir código orientado a objetos.

Por ejemplo, el siguiente programa escribe "Hola MundoBBx" en la consola:

0010 rem "persona.src

0020 declare Persona Persona!

0030 Persona!=new Persona()

0040 Persona!.saluda()

0050 stop

0060 rem

0070 class public Persona

0080 method public void saluda()

0090 print "Hola Mundobbx"

0100 methodend

0110 classend

Este documento provee un breve tutorial sobre cómo escribir código orientado a objetos en BBj.

Una clase BBj consiste en una colección de campos y métodos que son definidos por el programador. Un objeto es una "instancia" de una clase. Los objetos se pueden usar en BBj de la misma forma en que se pueden usar los objetos en Java.

Discutiremos varios ejemplos sin definir formalmente la sintaxis específica de cada instrucción. Para información detallada, puedes remitirte a la documentación en línea acerca de los siguientes comandos nuevos en BBj: class, classend, field, interface, interfaceend, method, methodend y methodret.

Ahora veremos alguna información general sobre clases y objetos. El resto de las secciones proveen ejemplos que demuestran cómo los objetos trabajan dentro del código BBj. En todos los ejemplos se utiliza el comando declare. El comando declare, ayuda al programador a encontrar errores por chequeo de tipo que pueden ocasionar errores en tiempo de ejecución, aún cuando no presenten errores en tiempo de escritura.

La definición de una clase puede estar dentro de un archivo que también contenga otro código de programa BBj. Cuando el intérprete BBj se va moviendo a través de un programa que contenga una clase, se saltará la definición de la clase (desde la instrucción class hasta la instrucción classend) de la misma manera que hace cuando encuentra una función definida por el usuario (instrucción def fn hasta fnend).

Una instancia de una clase se crea usando el operador new, de la misma forma como se hace en Java. Una instancia de una clase se denomina "objeto".

Una clase B puede extender otra clase A. En este caso, decimos que la clase A es una superclase de la clase B y a la inversa, decimos que la clase B es una subclase de clase A.

Un método de una clase define un "alcance" de variable. Las únicas variables que son compartidas entre el código dentro de un método de una clase y el código que llama al método de la clase, son aquellas variables que se han pasado mediante la lista de parámetros del método de la clase. Los campos de un objeto son accesibles dentro de todos los métodos del objeto.

Los métodos de un objeto se invocan usando la notación de punto que es común a muchos lenguajes orientados a objeto. Un método de una clase puede ser "public", "protected" o "private". Estos niveles de protección son similares a los que tienen otros lenguajes orientados a objetos.

Los campos de un objeto pueden ser "public" o "private". No se pueden acceder usando la notación de punto. Normalmente se accede a los campos usando métodos "accesores". BBj genera automáticamente un par de métodos "accesores" (get/set) para todo campo. Estos accesores tienen el nivel de protección con que fue declarado el campo al que se esta accediendo.

El símbolo especial # se usa dentro del código de método de un objeto para acceder a los campos y métodos de este objeto. Dentro de un método, el código usa #nombreCampo para acceder a un campo llamado nombreCampo y #nombreMetodo para acceder a un método llamado nombreMetodo.

Todo objeto tiene un campo implícito con el nombre this!. El código que reside dentro de un método no estático de un objeto puede acceder este campo implícito usando la notación #this!. Este campo implícito no lo puede acceder código que no sea parte del método no estático del objeto. Si la clase que define un objeto tiene una superclase, entonces asoma un segundo campo implícito llamado super! que se puede acceder dentro de cualquier método no estático usando la notación #super!. Las siguientes secciones nos entregarán ejemplos de cómo usar #this! y #super!.

Los campos de un objeto son fuertemente "tipados". Para cada campo se exige indicar el tipo como parte de la declaración del campo. Asimismo, los parámetros y el tipo de retorno de todos los métodos de un objeto son fuertemente tipados y el tipo de retorno se exigen como parte de la declaración de los métodos.

Con esta información general, veamos algún código de ejemplo.

Un simple Objeto: Escribiendo un Cheque

En esta sección: Introduciremos nuestro primer objeto. Escribiremos un cheque para Alfonso.

Este sencillo programa crea una instancia de un Cheque en la línea 0030 y entonces imprime el nombre del Receptor y el monto del cheque en la línea 0040. La definición de la clase Cheque se encuentre entre las líneas 0060 y 0160.

0010 rem ' pagable_01.src

0020 declare Cheque cheque!

0030 cheque! = new Cheque("Alfonso Sanchez", 50.44)

0040 print cheque!.getPago()," le fue pagado ",cheque!.getMonto()

0050 rem ' el control de flujo se salta la definicion de clase

0060 class public Cheque

0070 field public BBjString Pago$

0080 field public BBjNumber Monto

0090 method public Cheque(BBjString Pago$, BBjNumber Monto)

0100 #Pago$=Pago$

0110 #Monto=Monto

0120 methodend

0130 method public void setMonto(BBjNumber Monto)

0140 print "Monto del Cheque no se puede cambiar despues de quedar escrito"

0150 methodend

0160 classend

0170 print " ahora cambiaremos el destinatario del cheque"

0180 cheque!.setPago("Ivan Vargas")

0190 print cheque!.getPago()," le fue pagado ", cheque!.getMonto()

0200 print " e intentaremos cambiar el monto"

0210 cheque!.setMonto(43223.17)

0220 print cheque!.getPago()," le fue pagado ", cheque!.getMonto()

Algunas cosas que podemos notar:

  • Los nombres de los métodos y de campos son sensibles a mayúsculas y minúsculas. En la instrucción #Pago$=Pago$, la referencia de campo #Pago$ es sensible a mayúsculas y minúsculas, pero Pago$, una tradicional variable BBx string, no es sensible a mayúsculas y minúsculas.

  • Los campos de tipo BBjString y los nombres de parámetros siguen las reglas de nombre de variables tipo string (x$ o x!)

  • Los campos de tipo BBjInt y los nombres de parámetros siguen las reglas de nombre de variables tipo entero (x% o x!)

  • Los campos de tipo BBjNumber y los nombres de parámetros siguen las reglas de nombre de variables tipo numérico (x o x!)

  • Todo otro campo y nombre de parámetro sigues las reglas de nombre de tipo objeto (x!)

  • Los tipos de clases de todos los campos se definen en la instrucción field. Los tipos de clases de todos los parámetros de métodos se definen en la instrucción method. Al usar la instrucción declare, el programa puede también definir el tipo de clase de las variables locales. Cuando un tipo de clase se ha definido para una variable BBj forzará el tipo seguro para esta variable.

  • Debido a que Pago$ y Monto se declararon como "public", los accesores para estos campos también son públicos y son equivalentes a:

method public void setMonto(BBjNumber Monto)

#Monto = Monto

methodend

method public BBjNumber getMonto()

methodret #Monto

methodend

method public void setPago(BBjString Pago$)

#Pago$ = Pago$

methodend

method public BBjString getPago()

methodret #Pago$

methodend

  • El desarrollador puede sobrecargar el accesor para definir acciones más específicas.

Campos Privados: Clases Derivadas, Campos Protegidos y Privados

En esta sección: Crearemos una nueva clase NominaCheque que es una subclase de Cheque.

0010 rem ' porpagar_02.src

0020 class public Check

0030 field private BBjString Pago$

0040 field private BBjNumber Monto

0050 field private BBjNumber ProxNumeroCheque = 1

0060 field protected BBjNumber NumeroCheque

0070 method protected Check(BBjString Pago$, BBjNumber Monto)

0080 #Pago$ = Pago$

0090 #Monto = Monto

0100 #ProxNumeroCheque = #ProxNumeroCheque + 1

0110 methodend

0120 method public BBjNumber getMonto()

0130 methodret #Monto

0140 methodend

0150 method public BBjString getPago()

0160 methodret #Pago$

0170 methodend

0180 classend

0190 class public NominaCheque extends Cheque

0200 field private BBjNumber NetoMonto

0210 field private BBjNumber ImpuestoMonto

0220 method public NominaCheque(BBjString Nombre$, BBjNumber NetoMonto, BBjNumber ImpuestoMonto)

0230 #super!(Nombre$, NetoMonto - ImpuestoMonto)

0240 #NetoMonto = NetoMonto

0250 #ImpuestoMonto = ImpuestoMonto

0260 methodend

0270 method public BBjNumber getNetoMonto()

0280 methodret #NetoMonto

0290 methodend

0300 method public BBjNumber getImpuestoMonto()

0310 methodret #ImpuestoMonto

0320 methodend

0330 classend

0340 declare Cheque pagocheque!

0350 pagocheque! = new NominaCheque("Alfonso Sanchez", 278.35, 73.22)

0360 print pagocheque!

Algunas cosas que podemos notar:

  • La instrucción methodret se usa para retornar un valor desde un método del objeto. Si el método fue declarado para retornar void, entonces methodret no lleva ninguna expresión a continuación. Si el método fue declarada para retornar un tipo distinto de void, entonces a continuación de methodret debe ir una expresión cuya evaluación retorne el tipo declarado por el método.

  • Si un método fue declarado para retornar void, entonces se presenta un methodret implícito antes de la instrucción methodend. Esto significa que el método retornará al llamador cuando alcance la instrucciñon methodend. Si el método fue declarado para retornar un tipo distinto de void, entonces no habrá un methodret implícito. Si el código dentro del método en ejecución alcanza la instrucción methodend, en este caso se producirá un error.

  • La palabra reservada extends se usa para crear una clase derivada.

  • Los métodos que son declarados protected pueden ser utilizados por las clases derivadas, pero los métodos declarados private no.

  • Los campos pueden tener un expresión de inicialización opcional. Pero si no hay tal inicialización, entonces el valor inicial del campo dependerá de su tipo. Un campo de tipo BBjString se inicializará como cadena vacía (""). Un campo de tipo BBjNumber o de tipo BBjInt tendrán un valor inicial de cero. Todo otro campo será inicializado como nulo.

  • La definición de una clase debe comenzar con la instrucción class y finalizar con la instrucción classend.

  • Todo método debe comenzar con la declaración method y finalizar con methodend.

  • Los campos de objetos que contengan cadenas tradicionales BBx se declaran de tipo BBjString. Objetos que contengan números tradicionales BBx se declaran de tipo BBjNumber y BBjINt.

  • La variable pagocheque! es declarada como Cheque, pero se le asignó un valor que es PagoCheque. Esta asignación es válida dado que PagoCheque es por declaración un Cheque. Pero dado que pagocheque! es declarado como Cheque, solo puede llamar los métodos de Cheque.

La instrucción USE: USE y STATIC

En esta sección: Dividiremos un programa en dos archivos e introduciremos la instrucción use. Además, demostraremos el uso de métodos estáticos.

0010 REM ' accounting_03.src

0020 USE ::payable_03.src::Check

0030 USE ::payable_03.src::PayrollCheck

0040 declare Check aCheck!

0050 declare PayrollCheck bCheck!

0060 declare Check cCheck!

0070 aCheck! = new PayrollCheck("Jenny Jones", 507.44, 189.49)

0080 print aCheck!.getNextCheckNumber()

0090 bCheck! = new PayrollCheck("Timmy Malone", 50.44, 189.49)

0100 print aCheck!.getNextCheckNumber()

0110 print bCheck!.getNextCheckNumber()

0120 print Check.getNextCheckNumber()

0130 print PayrollCheck.getNextCheckNumber()

0140 print "we can create a PayrollCheck but we can not create a Check"

0150 cCheck! = new Check("Jenny Jones", 54884.99)

0010 rem ' payable_03.src

0020 class public Check

0030 field private BBjString Payee$

0040 field private BBjNumber Amount

0050 field private static BBjNumber NextCheckNumber = 1

0060 field protected BBjNumber CheckNumber

0070 method protected Check(BBjString Payee$, BBjNumber Amount)

0080 #Payee$ = Payee$

0090 #Amount = Amount

0100 #NextCheckNumber = #NextCheckNumber + 1

0110 methodend

0120 method public BBjNumber getAmount()

0130 methodret #Amount

0140 methodend

0150 method public BBjString getPayee()

0160 methodret #Payee$

0170 methodend

0180 method public static BBjNumber getNextCheckNumber()

0190 methodret #NextCheckNumber

0200 methodend

0210 classend

0220 class public PayrollCheck extends Check

0230 field private BBjNumber NetAmount

0240 field private BBjNumber TaxAmount

0250 method public PayrollCheck(BBjString Name$, BBjNumber NetAmount, BBjNumber TaxAmount)

0260 #super!(Name$, NetAmount - TaxAmount)

0270 #NetAmount = NetAmount

0280 #TaxAmount = TaxAmount

0290 methodend

0300 method public BBjNumber getNetAmount()

0310 methodret #NetAmount

0320 methodend

0330 method public BBjNumber getTaxAmount()

0340 methodret #TaxAmount

0350 methodend

0360 classend

Algunas cosas que podemos notar:

  • La instrucción use, indica al programa donde encontrar la definición de una clase que se encuentra en otro archivo. El programa debe contener una instrucción use por cada clase externa que se quiera utilizar en el programa.

  • La instrucción use consiste de dos partes. La primera parte es un nombre de archivo encerrado por los simbolos :: (dos puntos repetidos). La segunda parte es el nombre de la clase dentro de este archivo.

  • Si el nombre de archivo indicado en la instrucción use no es un nombre de archivo absoluto, entonces BBj intentará encontrar el archivo siguiendo las reglas habituales de búsqueda (primero el directorio actual y luego las rutas indicadas en el PREFIX).

  • Los objetos en BBj, soportan campos de tipo static y métodos de tipo static. En este ejemplo, los métodos aCheck!.getNextCheckNumber() y bCheck!.getNextCheckNumber() retornan el mismo valor. A medida que más cheques se crean, el valor retornado por getNextCheckNumber() cambiará.

  • El método getNextCheckNumber() puede ser llamado desde la clase usando el nombre de la clase. Así Check.getNextCheckNumber() y PayrollCheck.getNextCheckNumber() retornan el mismo valor que aCheck!.getNextCheckNumber() y bCheck!.getNextCheckNumber().

  • Un método de tipo static no puede acceder campos no estáticos.

  • Los campos y métodos de tipo static son estáticos dentro de una sesión BBj. Si el programa accounting_03.src se estuviera ejecutando en dos sesiones BBj, entonces cada sesión partirá con check number con valor 1; las dos sesiones incrementarán sus números de cheque de manera independiente.

  • Un campo de tipo static persiste a través de tiempo de vida de una sesión BBj.

  • La palabra reservada extends indica que la clase PayrollCheck es una subclase de la clase Check.

  • Un método declarado como private solo se puede acceder por la declaración de la clase (????? duda con la traducción y significado de esta afirmación ACLARAR). Este no puede ser accesado or una clase derivada y no puede ser accesada por código que se encuentre fuera de la clase.

  • Un método declarado como protected puede ser accesado por una clase derivada ya sea que la clase derivada se defina en el mismo archivo o en un archivo separado.

  • En el programa accounting_03.src la variable local aCheck! es declarada como de tipo Check. La variable local bCheck! es declarada como de tipo PayrollCheck. Dado que PayrollCheck "extiende" a Check, podemos asignar un nuevo PayrollCheck a aCheck! o a bCheck!

  • El constructor protected de Check no se puede invocar desde el archivo accounting_03.src

*****

Error Handling in Custom Object: seterr, setopts, and THROW

En esta sección demostraremosel uso de SETERR dentro de método de un objeto e introduciremos la instrucción THROW.

0010 rem errorHandling.src

0020 seterr errorHandler

0030 declare BBjString options$

0040 declare BBjString filename$

0050 declare Foo foo!

0060 input "permite depurar un programa publico? si=1/no=0 ",allow

0070 options$ = opts

0080 if allow then

0090 options$(1,1) = ior(options$(1,1),$08$)

0100 print "el programa detiene ejecucion en un error"

0110 else

0120 options$(1,1) = and(options$(1,1),$f7$)

0130 print "el programa pasara al controlador de error ante un error"

0140 endif

0150 setopts options$

0160 filename$ = "ljl;kj;"

0170 foo! = new Foo()

0180 foo!.openFile(filename$)

0190 end

0200 class public Foo

0210 method public void openFile(BBjString filename$)

0220 print "Foo.openFile() opening file: ",filename$

0230 seterr fileOpenError

0240 open (unt)filename$

0250 print "Apertura exitosa ",filename$

0260 methodret

0270 fileOpenError:

0280 print "entering error handler for Foo.fileOpenError"

0290 throw "Foo.openFile() no puede abrir el archivo: "+filename$,err

0300 methodend

0310 method public void bar()

0320 throw "throw in bar",22

0330 methodend

0340 classend

0350 errorHandler:

0360 print "entering error handler for program"

0370 print "received error:",errmes(-1)

0380 fileName$ = pgm(-1)

0390 retry

Algunas cosas que podemos notar:

  • El manejo de errores dentro de un método de un objeto es similar al manejo de errores en un programa llamado (programa público). Si el bit de SETOPTS que permite escapes en un programa llamado fue seteado, entonces al ocurrir un error en el programa llamado provocará que el flujo del programa "caiga" a la consola. Si no estuviera seteado este bit, entonces un error en el programa provocará que el flujo del programa "caiga" al programa que hace la llamada al programa. Estableciendo este bit en los modos posibles, afectará el comportamiento de un método en un objeto de la misma forma. Si el bit fue seteado, entonces un error en el método provocará que el flujo del programa "caiga" a consola. Si no fue seteado el bit, entonce el error provocará que el control retorne al programa que hizo la llamada.

  • El comando SETERR está disponible para usar en un método de un objeto. El efecto de esta este comando dentro de un método es similar al efecto de un SETERR en un programa llamada. El SETERR se mantiene activo hasta que el método retorna.

  • Al usar throw, un programa puede provocar que un error suceda programáticamente. Esto permite al programador especificar el número de error, así como el mensaje de error.

(contenido en proceso de elaboración...)