Decorator Pattern (VFP)
Por: VictorEspina
Contents |
Introducción
El patrón de diseño Decorador (Decorator Pattern) es una técnica que permite extender la funcionalidad de una clase sin hacer uso de la herencia. Si bien siempre ha habido una discusion sobre cuando usar Decoradores en lugar de subclases, lo cierto es que hay muchas ocasiones en donde el uso de un decorador puede solucionar un requerimiento dado y evitar el crecimiento desmedido del arbol de herencia en una clase dada.
Adicionalmente, el uso de decoradores anidados permite simular la función de multiherencia en lenguajes donde esta característica no es soportada (como es el caso de VIsual FoxPro).
Como funciona un Decorador
Un decorador no es mas que una clase que expone todos los métodos y propiedades de otra clase, ademas de los propios. Supongamos que tenemos una clase Usuario que implementa la siguiente interfaz:
DEFINE CLASS Usuario AS Custom Login = "" Password = "" nombreCompleto = "" PROCEDURE verificarPassword PROCEDURE Login PROCEDURE Logout ENDDEFINE
Como vemos, esta clase representa los datos básicos de un usuario y las operaciones que este puede realizar. Supongamos que ahora queremos incluir la posibilidad de que el usuario pueda cambiar su password. Una forma de hacer esto seria modificar la definición de la clase Usuario directamente, pero supongamos que por cualquier razón eso no es posible. Otra solución seria crear una subclase de Usuario y añadir la funcionalidad allí. Finalmente, otra opción es crear un decorador:
DEFINE CLASS Usuario2 AS Custom HIDDEN oUsuario Login = "" Password = "" nombreCompleto = "" PROCEDURE Init && Al crear una instancia de la clase se crea tambien una instancia de la clase real THIS.oUsuario = CREATE("Usuario") ENDPROC * Se programan Setters y Getters para cada propiedad de la clase real PROCEDURE Login_Access RETURN THIS.oUsuario.Login ENDPROC PROCEDURE Login_Assign(vNewVal) THIS.oUsuario.Login = m.vNewVal ENDPROC PROCEDURE Passsword_Access RETURN THIS.oUsuario.Password ENDPROC PROCEDURE Password_Assign(vNewVal) THIS.oUsuario.Password = m.vNewVal ENDPROC PROCEDURE nombreCompleto_Access RETURN THIS.oUsuario.nombreCompleto ENDPROC PROCEDURE nombreCompleto_Assign(vNewVal) THIS.oUsuario.nombreCompleto = m.vNewVal ENDPROC * Se implementan todos los metodos de la clase real, simplemente pasando el mensaje a la instancia de la clase que se creo en Init PROCEDURE verificarPassword(pcPwd) RETURN THIS.oUsuario.verificarPassword(pcPwd) ENDPROC PROCEDURE Login RETURN THIS.oUsuario.Login() ENDPROC PROCEDURE Logout RETURN THIS.oUsuario.Logout() ENDPROC * Se implementan los nuevos metodos que extienden la funcionalidad de la clase original PROCEDURE cambiarPassword(pcOldPwd, pcNewPwd) ENDDEFINE
Una vez definido, podemos usar la clase Usuario2 en cualquier lugar donde normalmente se usaria Usuario:
LOCAL oUsuario oUsuario = CREATE("Usuario2") oUsuario.Login() && Metodo en la clase Usuario oUsuario.cambiarPassword(cOld, cNew) && Método en la clase Usuario2
Como vemos, el decorador cumple dos funciones básicas:
- Implementa toda la interfaz de la clase que se esta decorando
- Implementa los nuevos metodos y propiedades que extienden la funcionalidad de la clase original
Tambien es obvio con este ejemplo que crear un decorador de una clase con una interfaz grande puede llegar a ser un trabajo tedioso. Justamente esta ultima parte es lo que me habia mantenido alejado del uso de decoradores.... hasta ahora.
La clase DecoratorPattern
La clase DecoratorPattern permite implementar un decorador de una clase cualquiera sin necesidad de recrear la interfaz del objeto que se desea decorar. En su lugar, la clase hace uso de las capacidades de reflexión de VFP y de la posibilidad en VFP de crear un accesor sobre la propiedad THIS para lograr el mismo efecto sin necesidad de escribir código. Utilizando la clase DecoratorPattern, la definición de Usuario2 se reduce a esto:
DEFINE CLASS Usuario2 AS DecoratorPattern className = "Usuario" * Se implementan los nuevos metodos que extienden la funcionalidad de la clase original PROCEDURE cambiarPassword(pcOldPwd, pcNewPwd) ENDDEFINE
Aunque la declaración es sustancialmente distinta y mas sencilla que la anterior, su uso continua siendo el mismo:
LOCAL oUsuario oUsuario = CREATE("Usuario2") oUsuario.Login() && Metodo en la clase Usuario oUsuario.cambiarPassword(cOld, cNew) && Método en la clase Usuario2
La clase DecoratorPattern funciona creando una instancia de la clase indicada en la propiedad className durante la inicialización de la instancia de la clase decorator y luego redirecciona hacia esa instancia las llamadas a miembros no implementados directamente por el decorador, de modo que cuando se invoca una propiedad o método definido en la clase original, la clase pasa el mensaje a la instancia de la clase original que se mantiene almacenada dentro de una propiedad oculta en la clase decoradora; si por el contrario se invoca un propiedad o método definido en la clase decoradora, entonces la clase asume el control y procesa el mensaje.
Encadenando decoradores
Para mostrar la forma en que la clase DecoratorPattern permite encadenar dos o mas decoradores, utilizaremos el famoso ejemplo de la rana. Supongamos que tenemos una clase que representa a una rana:
DEFINE CLASS Rana AS Custom PROCEDURE Come PROCEDURE Salta ENDDEFINE
Como vemos, hemos definido una rana que puede comer y saltar. Digamos que ahora queremos crear una rana que, ademas de comer y saltar, tambien pueda cantar:
DEFINE CLASS ranaCantora AS DecoratorPattern className = "Rana" PROCEDURE Canta ENDDEFINE
Pero no paremos aqui. Digamos que ahora queremos crear una rana que pueda Correr:
DEFINE CLASS ranaCorredora AS DecoratorPattern className = "Rana" PROCEDURE Corre ENDDEFINE
Veamos como funcionan estos decoradores por separado:
LOCAL oRana oRana = CREATE("Rana") oRana.Come() oRana.Salta() LOCAL oRanaCantora oRanaCantora = CREATE("ranaCantora") oRanaCantora.Come() && Implementado por la clase Rana oRanaCantora.Canta() && Implementado por la clase ranaCantora LOCAL oRanaCorredora oRanaCorredora = CREATE("ranaCorredora") oRanaCorredora.Salta() && Implementado por la clase Rana oRanaCorredora.Corre() && Implementado por la clase ranaCorredora
Y si ahora quisiéramos tener una rana que cante y corra ? Podemos lograrlo simplemente encadenando los decoradores:
LOCAL oSuperRana oSuperRana = CREATE("ranaCantora", CREATE("ranaCorredora")) oSuperRana.Come() && Implementado por la clase Rana oSuperRana.Canta() && Implementado por la clase ranaCantora oSuperRana.Corre() && Implementado por la clase ranaCorredora
El truco esta en que la clase DecoratorPattern recibe un parámetro opcional en su método constructor (Init). Este parámetro contiene la instancia de la clase que se desea decorar. Si no se indica (como en los ejemplos anteriores), la clase usa el valor de la propiedad className para crear una instancia de dicha clase automáticamente. Esto permite aplicar funcionalidad adicional a una clase en forma aditiva, es decir, según lo que se necesite (lo cual es una de las ventajas de esta técnica sobre el uso de subclases).
Usos en ambientes reales
La técnica del decorado tiene muchos usos, pero quizas el mas poderoso sea el de extender las capacidades de un sistema "cerrado". Por ejemplo, si se usa en conjunto con el patrón Factory, los decoradores podrían usarse como una forma de extender la funcionalidad de una clase para una implantación especifica de un sistema, sin necesidad de modificar el sistema mismo o recompilarlo.
Otro uso común de los decoradores es el de añadir funcionalidad puntual a una clase publicada en una libreria hecha por terceros y a cuyo código fuente no tenemos acceso y donde por razones de simplicidad no es posible aplicar sublassing.
Código Fuente
* DecoratorPattern * Clase para la implementacion del pattern Decorator en VFP * * Autor: Victor Espina * Fecha: Abril 2012 * * Uso: * DEFINE CLASS myDecoratedClass AS DecoratorPattern * className = "myRealClass" * [new properties] * [new methods] * ENDDEFINE * * a) Ejemplo 1: exteder la funcionalidad de una clase mediante el uso de un decorator * * DEFINE CLASS MyRealClass AS Custom * PROCEDURE realClassMethod * ENDDEFINE * * DEFINE CLASS myDecoratedClass AS DecoratorPattern * className = "MyRealClass" * PROCEDURE decoratedClassMethod * ENDDEFINE * * LOCAL oMyDecoratedObject * oMyDecoratedObject = CREATE("myDecoratedClass") * ?oMyDecoratedObject.decoratedClassMethod() * ?oMyDecoratedObject.realClassMethod() * * * b) Ejemplo 2: extender la funcionalidad de una clase mediante el uso de DOS decorators * * DEFINE CLASS mySpecialClass AS DecoratorPattern * className = "myRealClass" * PROCEDURE specialMethod * ENDDEFINE * * LOCAL mySpecialObject * oMySpecialObject = CREATE("mySpecialClass") * ?oMySpecialObject.specialClassMethod() * ?oMySpecialObject.realClassMethod() * * oMySpecialObject = CREATE("mySpecialClass", oMyDecoratedObject) && Decora una instancia de MyDecoratedClass en lugar de MyRealClass * ?oMySpecialObject.specialClassMethod() * ?oMySpecialObject.realClassMethod() * ?oMySpecialObject.decoratedClassMethod() * * DEFINE CLASS decoratorPattern AS Custom Decorator = .T. && Permite identificar las instancias de esta clase como "decoradores" oInstance = NULL && Instancia de la clase a decorar className = "" && Clase a decorar (si no se indica una instancia directamente) * Constructor * Si no se indica una instancia a decorar, se crea una automaticamente una * instancia de la clase indicada en la propiedad className PROCEDURE Init(poInstance) IF PCOUNT() = 1 THIS.oInstance = poInstance ELSE THIS.oInstance = CREATE(THIS.className) ENDIF ENDPROC * Accesor para THIS * Devuelve la referencia a la instancia que puede manejar el mensaje indicado. PROCEDURE THIS_Access(cMember) RETURN THIS.getHandler(cMember) ENDPROC * getHandler * Devueve una referencia a la instancia que puede manejar un mensaje dado, o NULL en cualquier otro caso PROCEDURE getHandler(cMember) DO CASE CASE PEMSTATUS(THIS, cMember, 5) && Se trata de un mensaje que puede manejar el decorador directamente ? RETURN THIS CASE PEMSTATUS(THIS.oInstance, "Decorator", 5) && El objeto decorado es a su vez un decorador? RETURN THIS.oInstance.getHandler(cMember) CASE PEMSTATUS(THIS.oInstance, cMember, 5) && El objeto decorado no es un decorador pero puede manejar el mensaje? RETURN THIS.oInstance OTHERWISE && Nadie en la cadena de decoracion puede manejar el mensaje, asi que devolvemos NULL RETURN NULL ENDCASE ENDPROC ENDDEFINE