El Modelo de Objetos de Gambas

1. Objetos y clases

Un objeto en Gambas es una estructura de datos que provee propiedades, variables, métodos y eventos.

Se accede a los objetos "por referencia, es decir, mediante un puntero hacia él o por medio de una variable cuyo valor es la dirección de memoria de la estructura de datos del objeto.

Puede ver la dirección de un objeto mediante la instrucción PRINT:

DIM aStr AS NEW String[]

PRINT aStr

(String[] 0x80dccf8)

La estructura de datos del objeto se describe a través de una clase.

1.1. Clases

Cada objeto de Gambas dispone de una clase que describe todas sus propiedades públicas, métodos y eventos. Esta clase también es un objeto, cuya clase es la clase llamada Class.

Una clase estática es una clase cuyos miembros son todos estáticos (ver más adelante). En Gambas, a una clase estática también se le llama módulo.

Una clase estática no se puede instanciar ya que crearía un objeto sin variables dinámicas, algo que carece de sentido.

Ejemplo

La clase System es estática: todos sus métodos son estáticos y sería imposible crear un objeto cuya clase fuera System.

Una clase virtual es una pseudo-clase oculta que no se puede manipular explícitamente.

1.2. Propiedades, métodos y variables

Las propiedades y los métodos permiten manipular la estructura de datos.

Una propiedad, un método o una variable pueden ser estáticos:

  • Una variable estática se compartirá con todas las instancias de la misma clase con el mismo valor no importa quien lo cambie.

  • Una propiedad o método estático sólo puede modificar variables estáticas.

Un método o una variable pueden ser tanto pública como privada. Una propiedad siempre es pública.

Los símbolos privados sólo se pueden usar en dentro de la clase que los define. Los símbolos públicos se pueden usar en cualquier lugar, siempre que disponga de una referencia que apunte a ese objeto.

1.3. Referencias

Gambas no cuenta con un recolector de basura, pero cada objeto dispone de un contador de referencias que se incrementa cada vez que se lo referencia con una nueva variable, arreglo, colección u otro objeto y se decrementa cuando se libera alguna de sus referencias.

El contador de referencias de un objeto tiene valor cero al momento de la instanciación y cuando vuelve a cero, después de la liberación de sus referencias, se elimina dicho objeto.

1.4. Objetos inválidos

Un objeto puede llegar a ser inválido porque, por ejemplo, está enlazado a un objeto interno que Gambas no gestiona y que fue destruido.

El intento de usar un objeto inválido provoca el lanzamiento de un error.

1.5. Métodos especiales

Los métodos especiales se declaran en clases cuyos nombres comienzan con un caracter guión bajo y el intérprete los invoca en las siguientes situaciones:

  • Cuando se crea un objeto.

  • Cuando se libera (destruye) un objeto.

  • Cuando se carga su clase.

  • Cuando se descarga su clase.

  • Cuando se usa un objeto como si fuera un arreglo.

  • Cuando se enumera el objeto.

  • Cuando se usa un objeto como si fuera una función.

  • Cuando se intenta usar un método o propiedad desconocido.

Para obtener más información, véase también Métodos especiales y Métodos especiales.

2. Eventos y observadores

2.1. Eventos

Los eventos son señales que un objeto envía cuando algo sucede en su interior.

Si un objeto lanza eventos mantendrá una referencia a sus observadores u objeto padre.

Este observador es otro objeto que implementa manejadores de eventos. Un manejador de eventos es tan sólo un método público que se invoca cada vez que se lanza el evento.

De forma predefinida, el observador es el objeto actual donde se declaró el objeto recién instanciado.

Para lanzar eventos un objeto debe tener un nombre de evento que se asigna al momento de su instanciación, al usar la instrucción NEW, la palabra clave AS y es el prefijo (del nombre) de todos los métodos manejadores de eventos.

Ejemplo

El código siguiente crea un control Button que lanzanrá eventos.

DIM hButton AS Button

hButton = NEW Button(ME) AS "ButtonEventName"

Si no se especifica un nombre de evento, el objeto no querrá lanzarlos.

2.2. Bloqueo de objectos

Se puede bloquear un objeto para que ya no lance eventos y se lo puede desbloquear para que vuelva a lanzarlos. Véanse los métodos Object.Lock y Object.Unlock.

El manejador de eventos puede cancelar algunos eventos por medio de la instrucción STOP EVENT. El efecto de esta cancelación depende del evento.

Se bloquea automáticamente un objeto durante la ejecución de su constructor: no puede enviar ni recibir ningún evento.

2.3. Observadores

Los Observadores son objetos que le permiten interceptar eventos lanzados por otros objetos, es decir que los "observan".

Puede interceptar eventos justo antes o después de que sean lanzados.

Por cada evento interceptado el observador lanzará un evento con el mismo nombre y los mismos argumentos.

Puede cancelar el evento original al usar STOP EVENT dentro del manejador de evento del observador.

Ejemplo

PRIVATE $hButton as Button
PRIVATE $hObserver AS Observer

PUBLIC SUB Form_Load()
  $hButton = NEW Button(ME) AS "Button"
  $hObserver = NEW Observer(hButton) AS "Observador"
END

PUBLIC SUB Observador_Click()
  DEBUG "Se hizo clic en el botón. ¡Cancelo el evento!"
  STOP EVENT
END

PUBLIC SUB Button_Click()
  DEBUG "No deberías verme."
END

3. Herencia

La herencia es la manera que tiene una clase de convertirse en una versión especializada de otra clase.

3.1. ¿Qué se hereda?

La clase hereda de su "padre" cada métodos, propiedad, constante y evento.

Debe utilizar la palabra clave ME para acceder a los elementos heredados, desde el interior de la clase (hija).

3.2. ¿Qué clase puede ser una clase padre?

Puede heredar de cualquier clase ¡incluso de una clase nativa!

Por ejemplo, puede crear una clase personalizada llamada MyListBox que hereda de ListBox pero permite asociar una etiqueta con cada elemento de la lista.

Observe que no puede usar INHERITS en un archivo de clase de un formulario, porque los formularios ya heredan de la clase Form.

El árbol de herencia no puede tener una profundidad mayor a 16 que es el valor literal de una constante especificada dentro del intérprete de Gambas (ndt: esto no es una limitación, si construye un árbol de herencia muy profundo significa que ha hecho un mal diseño).

3.3. Virtual dispatching

Cuando se invoca un método o se accede a una propiedad por medio de una referencia a un objeto, Gambas siempre utiliza virtual dispatching.

Esto significa que siempre se utiliza la clase verdadera del objeto en lugar del tipo de la variable que referencia al objeto (como ocurría en Gambas 1.0).

3.4. Herencia y constructor

En contraste con todos los lenguajes de objetos que conozco, cada clase en la jerarquía de herencia utiliza los parámetros pasados al constructor.

Supongamos que tenemos el siguiente árbol de herencia:

MyListBox ---inherits--> ListBox ---inherits---> Control
  • Control._new() no existe.

  • ListBox._new() toma un parámetro: el control padre.

  • MyListBox._new() toma un parámetro: un nombre (es sólo un ejemplo).

De modos que NEW MyListBox tomará dos parámetros.

  • El primero se enviará a to MyListBox._new().

  • El segundo a ListBox._new().

Sea precavido: se llamará primero a ListBox._new(), de modo de asegurar que el control ListBox existe cuando se ejecuta MyListBox._new().

Entonces, la manera de crear un control MyListBos es esta:

hMyListBox = NEW MyListBox("Nombre", hContainer)

Así que el orden de los argumentos es el siguiente:

  • Los argumentos obligatorios se utilizan primero y luego, en caso de haberse proporcionado, los opcionales.

  • Se especifican primero los argumentos de las clases en niveles más altos en el árbol.

¡En Gambas 2.0 el orden de los argumentos era el inverso!

Por ejemplo, si tiene la siguiente jerarquía de herencia:

MyForm --> Form --> Window

Siendo el constructor de MyForm:

Sub _new(PrimerArg As String, SegundoArg as Integer, Optional TercerArg As Integer)

Nota: el constructor Form no toma argumentos y el constructor Window toma un argumento opcional padre.

La firma del último constructor será::

New MyForm(PrimerArg As String, SegundoArg As Integer, Optional Parent As Control, Optional TercerArg As Integer)

De forma general, el orden de los argumentos para una jerarquía de herencia de tres niveles es:

  • Argumentos obligatorios del constructor abuelo.

  • Argumentos obligatorios del constructor padre.

  • Argumentos obligatorios del constructor hijo.

  • Argumentos opcionales del constructor abuelo.

  • Argumentos opcionales del constructor padre.

  • Argumentos opcionales del constructor hijo.

3.5. Redefinición de símbolos

Al redefinir un símbolo, su signatura en la clase hija debe ser la misma que en la clase padre.

Estas son las reglas:

  • Un símbolo dinámico se debe redefinir con otro símbolo dinámico y un símbolo estático con otro estático.

  • Un método se debe redefinir con otro que posea exactamente la misma signatura: la misma cantidad y tipo de datos en sus parámetros, el mismo tipo de retorno (si tiene).

  • Una propiedad de lectura/escritura se debe redefinir con una propiedad de lectura/escritura con el mismo tipo de datos.

  • Una propiedad de sólo lectura se debe redefinir con una propiedad de sólo lectura con el mismo tipo de datos.

  • Una constante se debe redefinir con una constante con el mismo tipo de datos.

4. Components

Gambas component are external shared libraries written in C, C++ or in Gambas that add new functions and/or classes to the Gambas interpreter.

Classes are grouped according to the component they come from.

4.1. Default internal component

The interpreter includes an internal component named gb that defines all standard classes of the language.

This component is always loaded by default, and can be considered as part of the language.

4.2. Symbol tables

Each component has its own private class symbol table, so that class names do not conflict.

4.3. Global symbol table

So that components can work together, there is a global symbol table, where all classes exported by components and all classes exported by the current project are stored.

If there is a name conflict in this global symbol table, the last loaded class overrides the previous loaded class with the same name, by using inheritance. In other words, the overriding class extends the overriden one.

This last feature can be used for:

  • Extending an already declared class by adding new methods or properties to it. For example, the gb.qt4 Application class reimplements the gb Application class.

  • Overriding the methods of an already declared class. For example, the gb.form.dialog component replaces most of the static methods of the Dialog class.

4.4. Project symbol table

Your project has its own private symbol, like any component, and can export any of its classes to the global symbol table by using the EXPORT keyword.

The project classes are loaded after all components. So your exported class can override any exported classes declared in any component.

See also