inicio | documentación | contacto
Version para imprimir Imprimir
Inicio » Documentación » Atributos dinamicos

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 :

  1. 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.
  2. 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.

Última modificación: miércoles 14 de mayo de 2008 15:35:43
Fecha de creación: viernes 28 de diciembre de 2007 10:51:25
Categorías: