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