Decorator Pattern (VFP)

From codeWiki
Jump to: navigation, search

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

Mas Información

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox