Atributos dinamicos
Un atributo dinámico es aquel que cuando se lee o modifica, es interceptado por un método especial que permite ejecutar código justo antes de leer o modificar dicho atributo. Este método puede alterar el valor, validar que sea correcto o simplemente pintar un mensaje.
Lectura dinámica
Para ejecutar un método cada vez que se lea un atributo, tenemos dos opciones :
- No crear un atributo y, en su lugar, crear un método con el nombre del atributo deseado y que éste se encargue de devolver el valor.
- Crear un atributo que guarde el valor y crear un método con el nombre
get.nombreDelAtributo()que devuelva ese valor.
Es decir, supongamos que queremos utilizar un atributo dinámico llamado color y queremos que se ejecute algo de código cuando sea leido, basta con crear un método su mismo nombre:
def main
println Prueba.color
end
object Prueba
def color()
println "Estas leyendo el atributo color"
return "Azul"
end
endobject
El resultado es:
Estas leyendo el atributo color Azul
El problema de este sistema es que tiene un gran inconveniente. Dado que no existe un atributo real donde se guarde el valor, no podemos hacer asignaciones. El siguiente código provocará un fallo al no existir el atributo color:
Prueba.color = 2
Podríamos pensar que si creamos un atributo llamado color arreglamos el problema, pero no es así. ¿Qué sucede si, además del método color(), tenemos también un atributo color con el mismo nombre? Podremos hacer asignaciones, pero en las lecturas, el sistema leerá primero el atributo color antes que el método con su mismo nombre, por lo que perderíamos la característica de atributo dinámico.
El siguiente código es un ejemplo de una mala implementación en el que se pretende crear un atributo dinámico llamado foo, el cual cada vez que es leído, se incrementa en 1 su valor. Para hacer esto, necesitamos tener un atributo donde se guarde realmente el valor para, cada vez que es leído, poder sumarle 1 antes de devolverlo.
En esta mala implementación, tenemos un atributo foo y un método foo().
def main
println Prueba.foo
println Prueba.foo
println Prueba.foo
end
object Prueba
// Inicialmente vale 0
var foo = 0
def foo()
// Incrementamos en 1 su valor
foo = foo + 1
// Devolvemos el valor real del atributo
return foo
end
endobject
La salida del programa debería ser 1 2 3. Sin embargo, es 0 0 0. Esto es así porque el atributo foo tiene preferencia sobre el método foo() cuando se llama a Prueba.foo.
Para solucionar este problema, tenemos la segunda opción: crear un método llamado get.foo que no colisiona con el atributo foo:
En esta correcta implementación, tenemos un atributo foo y un método get.foo().
def main
println Prueba.foo
println Prueba.foo
println Prueba.foo
end
object Prueba
// Inicialmente vale 0
var foo = 0
def get.foo()
// Incrementamos en 1 su valor
foo = foo + 1
// Devolvemos el valor real del atributo
return foo
end
endobject
Ahora, el método get.foo() tiene preferencia sobre el atributo foo (solo cuando es leído desde fuera del objeto).
El resultado es ahora el esperado:
1 2 3
En resumen, podemos interceptar la lectura de un atributo creando un método con el mismo nombre solo si no tenemos ya un atributo con ese nombre. Si lo tuviéramos, entonces la solución sería crear un método con el nombre get.atributoInterceptado.
Escritura dinámica
Para que se ejecute un método cada vez que modificamos un atributo, tan solo tenemos que crear un método con el siguiente nombre: set.nombreDelAtributo(nuevoValor). Si creamos este método, el atributo no será modificado, por esta razón, será labor de este nuevo método modificar de manera manual el atributo. Si este método no hiciera nada, el atributo no sería modificado.
Ejemplo:
def main
println Prueba.foo
Prueba.foo = 2
println Prueba.foo
end
object Prueba
var foo = 0
def set.foo(nuevoValor)
println "Modificando foo con el nuevo valor "+nuevoValor
foo = nuevoValor
end
endobject
El resultado es:
0 Modificando foo con el nuevo valor 2 2
La utilidad de interceptar la modificación de atributos es muy amplia. Podemos validar que el nuevo valor cumpla unos requisitos para que sea modificado.
En el siguiente ejemplo, solo se permite modificar el atributo si el valor es un número comprendido entre 0 y 10. Si no lo es, no modifica el método. De esta manera, nos aseguramos de que el atributo nunca tendrá un valor fuera del rango 0-10. Al menos, si se modifica fuera del objeto, ya que desde dentro del mismo (es decir, desde otros métodos), podemos alterar el atributo directamente sin pasar por el método.
def main
Prueba.nota = 2
Prueba.nota = 12
println Prueba.nota
end
object Prueba
var nota = 0
def set.nota(nuevoValor)
// Si el nuevo valor esta dentro del rango 0-10
if nuevoValor >= 0 && nuevoValor <=10
// Entonces modificamos el valor
nota = nuevoValor
endif
end
endobject
El resultado de Prueba.nota al finalizar el programa es 2, ya que el nuevo valor 12 no llega a modificarse nunca.
Ejemplo final completo de atributo dinámico
El siguiente ejemplo es un objeto con un atributo llamado dinamico el cual tiene implementado los métodos set y get que interceptan su lectura y escritura. El ejemplo no hace nada, pero sirve como plantilla o punto de partida para una implementación más compleja.
def main
Ejemplo.dinamico = "HOLA"
println Ejemplo.dinamico
end
object Ejemplo
var dinamico
// Interceptor de lectura
def get.dinamico
return dinamico
end
// Interceptor de escritura
def set.dinamico(dinamico)
#self.dinamico = dinamico
end
endobject
Notar el usar de #self.dinamico en la asignación. Esto se hace así para distinguir la variable local dinamico recibida como parámetro del propio atributo del objeto que tiene el mismo nombre. Dado que las variables locales tienen preferencia sobre cualquier otra (cuando tienen el mismo nombre), es necesario utilizar #self para referenciar el atributo y distinguirlo de la variable local. Esto solo es necesario en el método set.dinamico, ya que en el otro método get.dinamico, al no existir ninguna variable local llamada dinamico, se toma por defecto el atributo del objeto. Ver orden de prioridad de acceso a variables en metodos para más información.