28 de abril de 2017

Patrones de diseño - El decorador

Artículo original: Design Patterns - The Decorator
http://weblogs.foxite.com/andykramek/archive/2006/12/31/3072.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York


El decorador describe una solución al problema de agregar una funcionalidad a un objeto sin cambiar realmente nada del código en el objeto.

¿Cómo reconozco cuándo necesito un decorador?

La definición formal del decorador, dada en "Design Patterns, Elements of Reusable Object-Oriented Software" por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides es:

Adjuntar responsabilidad adicional a un objeto dinámicamente.

La necesidad de cambiar dinámicamente la funcionalidad o comportamiento de un objeto surge típicamente en una de estas dos situaciones. Primero, cuando el código fuente del objeto no está disponible, puede ser que el objeto sea un control ActiveX o una biblioteca de clases de terceras partes que estemos utilizando. Segundo, cuando la clase es ampliamente utilizada; pero la responsabilidad específica, se necesita sólo en una o más situaciones particulares y pueda ser inapropiado agregar el código a la clase.

En los artículos precedentes hemos visto varias vías para determinar la cantidad de la tasa a aplicar al 'precio' en función de la 'localidad'. El objeto para el cálculo básico en cada caso trabaja exponiendo un método llamado CalcTax(), que toma dos parámetros, un valor y un tipo de impuesto y devuelve la tasa calculada. Afrontamos el problema de tratar con la tasa en el formulario, utilizando un patrón de puente para separar la implementación de la interfaz. Sin embargo, como hemos visto rápidamente, esto no fue suficientemente flexible para asociar diferentes índices de tasa a diferentes localidades. Ambos, los patrones de estrategia y de cadena de responsabilidad pueden manipular este aspecto, sin embargo, ambas soluciones implican crear subclases de nuestra clase base de cálculos de tasas.

El patrón decorador nos permite solucionar el mismo problema sin necesidad de crear subclases. En lugar de definir un nuevo objeto, que tiene exactamente la misma interfaz como el cálculo de la tasa, pero que incluye además el código necesario para determinar la tasa apropiada para una localización dada. El objeto "mira" justo como el cálculo de la tasa como su cliente; pero, porque también guarda una referencia al objeto de cálculo de tasa, puede "pre-procesar" cualquier requerimiento de un índice y luego, simplemente puede dar la implementación real cuando esté lista.

¿Cuáles son los componentes del decorador?

Un decorador, tiene dos requerimientos esenciales. Primero, necesitamos la clase implementación que define la interfaz y el núcleo de funcionalidad. Segundo, necesitamos un decorador que reproduzca la interfaz de implementación, y guarde una referencia a el. El objeto cliente ahora dirige las llamadas que podrían ir directamente a la implementación, en su lugar, del objeto decorador. La estructura básica del patrón decorador es:

Este patrón es en realidad un puente extendido. Debido a que el cliente está afectado, puede dirigir el objeto decorador como si realmente fuera la implementación al final del puente estándar, porque la interfaz de los dos es la misma. Debido a que la implementación está afectada, la petición mira exactamente igual que si fuera directamente desde el cliente. En otras palabras, no necesita nunca conocer incluso que el decorador existe.

¿Cómo implementar un decorador?

Una nueva clase, nombrada "cntDecorador" ha sido definida para implementar el ejemplo del decorador. Tiene tres propiedades y un método que se muestran a continuación:

NombreDescripción
cImpclassNombre de la clase a instanciar para este decorador.
cImplibBiblioteca de clases para la implementación de la clase a instanciar.
oCalculatorReferencia de objeto para instanciar la clase que es el implementador real al método CalcTax(). Este objeto es el instanciado en el Init() del decorador.
CalcTaxImplementación del decorador para el método equivalente en el implementador 'real'

La clase decorador es realmente muy simple. Al crearse, instancia una clase implementación real, la cual está definida por sus propiedades: nombre de clase y biblioteca. Además implementa un método operacional (en este caso el método CalcTax()), nombrado de la misma forma y que tiene la misma estructura que el método real a implementar.

El código en el método CalcTax() del decorador está muy claro. Espera recibir dos parámetros, el ID de localización como una cadena, y el precio para el que la tasa es requerida. Observe que éstos no son los mismos parámetros que se requieren por el método CalcTax() en el método real. (Aquí necesitamos pasar un precio y el índice de la tasa para aplicarle). El método CalcTax() del decorador determina el índice adecuado basándose en el ID de localización y luego llama al método CalcTax() de su objeto de implementación pasando el parámetro 'real'. El valor devuelto es justamente pasado al cliente sin ninguna modificación adicional.

LPARAMETERS tcLocation, tnPrice
LOCAL lnRate, lnTax
STORE 0 TO lnRate, lnTax

*** Determine el índice correcto
DO CASE 
  CASE tcLocation = '01'
    lnRate = 5.75
  CASE tcLocation = '02'
    lnRate = 5.25
  CASE tcLocation = '03'
    lnRate = 0.00
  OTHERWISE
    lnRate = 0
ENDCASE
*** Ahora, pase la llamada al objeto real
lnTax = This.oCalculator.CalcTax( tnPrice, lnRate )
*** Y devuelva el resultado
RETURN lnTax

Para utilizar el decorador, apenas necesitamos un pequeño cambio en el cliente. En lugar de instanciar el objeto real para el cálculo, en su lugar instancia el objeto decorador. Sin embargo, recuerde que la forma de la llamada para el método decorador es idéntico al método 'real', por lo que no hace falta ningún cambio.

LPARAMETERS tcContext, tnPrice
LOCAL lnTax
WITH ThisForm
  *** Verifica que tenemos disponible el Decorador
  IF VARTYPE( This.oCalc ) # "O"
    *** No lo tenemos, entonces lo creamos
    .oCalc = NEWOBJECT( 'cntDecorator', 'ch15.vcx' )
  ENDIF
  *** Ahora, sólo llamamos su método CalcTax() 
  *** y pasa ambos parámetros: localidad y precio
  lnTax = .oCalc.CalcTax( tcContext, tnPrice )
  IF ISNULL( lnTax )
    *** No puede procesar la petición
    MESSAGEBOX( 'No puede procesar la localización', 16, 'Error' )
    lnTax = 0
  ENDIF
  RETURN lnTax
ENDWITH

Aunque es un ejemplo muy sencillo, puede ver qué fácil puede ser extender la funcionalidad del decorador. Un problema muy frecuente, en la vida real, en que este patrón puede ser utilizado, es cómo proporcionar la implementación de validación específica a una rutina genérica de Guardar.

Resumen de patrón de decorador

El patrón de decorador es utilizado cuando deseamos modificar el comportamiento básico de una instancia específica sin la necesidad de crear una nueva subclase, o cambiar el código en el original. El decorador, esencialmente actúa como un preprocesador para su implementación interponiendo su cliente y la implementación.

No hay comentarios. :

Publicar un comentario