En programación orientada a objetos , el patrón decorador es un patrón de diseño que permite agregar comportamiento a un objeto individual , de forma dinámica, sin afectar el comportamiento de otras instancias de la misma clase . [1] El patrón decorador suele ser útil para adherirse al Principio de Responsabilidad Única , ya que permite dividir la funcionalidad entre clases con áreas de interés únicas [2] , así como al Principio Abierto-Cerrado , al permitir que la funcionalidad de una clase se extienda sin modificarla. [3] El uso del decorador puede ser más eficiente que la subclasificación, porque el comportamiento de un objeto se puede aumentar sin definir un objeto completamente nuevo.
El patrón de diseño decorador [4] es uno de los veintitrés patrones de diseño conocidos ; estos describen cómo resolver problemas de diseño recurrentes y diseñar software orientado a objetos flexible y reutilizable, es decir, objetos que son más fáciles de implementar, cambiar, probar y reutilizar.
Al utilizar subclases, las distintas subclases extienden una clase de distintas maneras. Sin embargo, una extensión está vinculada a la clase en tiempo de compilación y no se puede modificar en tiempo de ejecución. [ cita requerida ]
Definir Decorator
objetos que
Component
) de forma transparente reenviándole todas las solicitudes.Esto permite trabajar con distintos Decorator
objetos para ampliar la funcionalidad de un objeto de forma dinámica en tiempo de ejecución.
Consulte también el diagrama de secuencia y clase UML que aparece a continuación.
El patrón decorador se puede utilizar para extender (decorar) la funcionalidad de un determinado objeto de forma estática o, en algunos casos, en tiempo de ejecución , independientemente de otras instancias de la misma clase , siempre que se realice algún trabajo preliminar en tiempo de diseño. Esto se logra diseñando una nueva clase Decorator que envuelva a la clase original. Esta envoltura se puede lograr mediante la siguiente secuencia de pasos:
Este patrón está diseñado para que se puedan apilar múltiples decoradores uno sobre otro, agregando cada vez una nueva funcionalidad a los métodos reemplazados.
Tenga en cuenta que los decoradores y el objeto de clase original comparten un conjunto común de características. En el diagrama anterior, el método operación() estaba disponible tanto en la versión decorada como en la no decorada.
Las características de decoración (por ejemplo, métodos, propiedades u otros miembros) suelen definirse mediante una interfaz, mixin (también conocido como rasgo ) o herencia de clase que comparten los decoradores y el objeto decorado. En el ejemplo anterior, la clase Component es heredada tanto por ConcreteComponent como por las subclases que descienden de Decorator .
El patrón decorador es una alternativa a la subclasificación . La subclasificación agrega un comportamiento en tiempo de compilación y el cambio afecta a todas las instancias de la clase original; la decoración puede proporcionar un nuevo comportamiento en tiempo de ejecución para los objetos seleccionados.
Esta diferencia se vuelve más importante cuando existen varias formas independientes de extender la funcionalidad. En algunos lenguajes de programación orientados a objetos , las clases no se pueden crear en tiempo de ejecución y, por lo general, no es posible predecir, en tiempo de diseño, qué combinaciones de extensiones serán necesarias. Esto significaría que se tendría que crear una nueva clase para cada combinación posible. Por el contrario, los decoradores son objetos que se crean en tiempo de ejecución y se pueden combinar según el uso. Las implementaciones de flujos de E/S de Java y .NET Framework incorporan el patrón decorador.
Como ejemplo, considere una ventana en un sistema de ventanas . Para permitir el desplazamiento por el contenido de la ventana, se pueden agregar barras de desplazamiento horizontales o verticales , según corresponda. Suponga que las ventanas están representadas por instancias de la interfaz Window y suponga que esta clase no tiene ninguna funcionalidad para agregar barras de desplazamiento. Se podría crear una subclase ScrollingWindow que las proporcione, o crear un ScrollingWindowDecorator que agregue esta funcionalidad a los objetos Window existentes . En este punto, cualquiera de las dos soluciones sería buena.
Ahora, supongamos que uno también desea la capacidad de agregar bordes a las ventanas. Nuevamente, la clase Window original no tiene soporte. La subclase ScrollingWindow ahora plantea un problema, porque ha creado efectivamente un nuevo tipo de ventana. Si uno desea agregar soporte de borde a muchas ventanas pero no a todas , debe crear subclases WindowWithBorder y ScrollingWindowWithBorder , etc. Este problema empeora con cada nueva característica o subtipo de ventana que se agrega. Para la solución del decorador, se crea un nuevo BorderedWindowDecorator . Cualquier combinación de ScrollingWindowDecorator o BorderedWindowDecorator puede decorar ventanas existentes. Si la funcionalidad necesita agregarse a todas las ventanas, la clase base puede modificarse. Por otro lado, a veces (por ejemplo, utilizando marcos externos) no es posible, legal o conveniente modificar la clase base.
En el ejemplo anterior, las clases SimpleWindow y WindowDecorator implementan la interfaz Window , que define el método draw() y el método getDescription() que se requieren en este escenario para decorar un control de ventana.
Agregar o quitar decoradores a pedido (como al presionar un botón) es un patrón de interfaz de usuario común, que a menudo se implementa junto con el patrón de diseño Command . Por ejemplo, una aplicación de edición de texto puede tener un botón para resaltar texto. Al presionar el botón, los glifos de texto individuales seleccionados actualmente se envolverán en decoradores que modificarán su función draw(), lo que hará que se dibujen de manera resaltada (una implementación real probablemente también usaría un sistema de demarcación para maximizar la eficiencia).
La aplicación o eliminación de decoradores en función de los cambios de estado es otro caso de uso común. Según el alcance del estado, los decoradores se pueden aplicar o eliminar en masa. De manera similar, el patrón de diseño State se puede implementar utilizando decoradores en lugar de objetos subclasificados que encapsulen la funcionalidad cambiante. El uso de decoradores de esta manera hace que el estado interno y la funcionalidad del objeto State sean más compositivos y capaces de manejar una complejidad arbitraria.
La decoración también se utiliza a menudo en el patrón de diseño Flyweight . Los objetos Flyweight se dividen en dos componentes: un componente invariante que se comparte entre todos los objetos flyweight; y un componente decorado variante que puede compartirse parcialmente o no compartirse por completo. Esta partición del objeto flyweight tiene como objetivo reducir el consumo de memoria. Los decoradores normalmente también se almacenan en caché y se reutilizan. Todos los decoradores contendrán una referencia común al objeto invariante compartido. Si el estado decorado es solo parcialmente variante, entonces los decoradores también se pueden compartir hasta cierto punto, aunque se debe tener cuidado de no alterar su estado mientras se utilizan. UITableView de iOS implementa el patrón flyweight de esta manera: las celdas reutilizables de una vista de tabla son decoradores que contienen referencias a un objeto de fila de vista de tabla común, y las celdas se almacenan en caché/reutilizan.
La aplicación de combinaciones de decoradores de diversas formas a una colección de objetos presenta algunos problemas a la hora de interactuar con la colección de una manera que aproveche al máximo la funcionalidad añadida por los decoradores. El uso de patrones de adaptador o visitante puede resultar útil en estos casos. La interacción con varias capas de decoradores plantea desafíos adicionales y la lógica de los adaptadores y visitantes debe diseñarse para tenerlos en cuenta.
Los decoradores permiten un enfoque compositivo en lugar de uno jerárquico descendente para ampliar la funcionalidad. Un decorador permite añadir o modificar el comportamiento de una interfaz en tiempo de ejecución. Se pueden utilizar para encapsular objetos en una combinación arbitraria de varias capas. Hacer lo mismo con las subclases significa implementar redes complejas de herencia múltiple, lo que es ineficiente en términos de memoria y, en un momento determinado, simplemente no se puede escalar. Del mismo modo, intentar implementar la misma funcionalidad con propiedades sobrecarga cada instancia del objeto con propiedades innecesarias. Por las razones anteriores, los decoradores suelen considerarse una alternativa eficiente en términos de memoria a la subclasificación.
Los decoradores también se pueden utilizar para especializar objetos que no se pueden subclasificar, cuyas características deben modificarse en tiempo de ejecución (como se menciona en otra parte) o, en general, objetos que carecen de alguna funcionalidad necesaria.
El patrón de decorador también puede ampliar el patrón de fachada . Una fachada está diseñada para interactuar simplemente con el sistema complejo que encapsula, pero no agrega funcionalidad al sistema. Sin embargo, la envoltura de un sistema complejo proporciona un espacio que puede usarse para introducir nueva funcionalidad basada en la coordinación de subcomponentes en el sistema. Por ejemplo, un patrón de fachada puede unificar muchos diccionarios de diferentes idiomas bajo una interfaz de diccionario multilingüe. La nueva interfaz también puede proporcionar nuevas funciones para traducir palabras entre idiomas. Este es un patrón híbrido: la interfaz unificada proporciona un espacio para la ampliación. Piense en los decoradores como si no se limitaran a envolver objetos individuales, sino que también fueran capaces de envolver grupos de objetos en este enfoque híbrido.
Como alternativa al patrón decorador, se puede utilizar el adaptador cuando el contenedor debe respetar una interfaz particular y debe soportar un comportamiento polimórfico , y la Fachada cuando se desea una interfaz más fácil o más simple con un objeto subyacente. [6]
En el diagrama de clases UML anterior , la clase abstracta mantiene una referencia ( ) al objeto decorado ( ) y le reenvía todas las solicitudes ( ). Esto hace que sea transparente (invisible) para los clientes de .Decorator
component
Component
component.operation()
Decorator
Component
Las subclases ( Decorator1
, Decorator2
) implementan un comportamiento adicional ( addBehavior()
) que se debe agregar a Component
(antes/después de reenviarle una solicitud).
El diagrama de secuencia muestra las interacciones en tiempo de ejecución: El Client
objeto funciona a través de objetos Decorator1
y Decorator2
para extender la funcionalidad de un Component1
objeto.
Las Client
llamadas operation()
a Decorator1
, que reenvía la solicitud a Decorator2
. Decorator2
se ejecuta addBehavior()
después de reenviar la solicitud a Component1
y regresa a Decorator1
, que se ejecuta addBehavior()
y regresa a Client
.
Esta implementación se basa en la implementación anterior a C++98 del libro.
#include <iostream> #include <memoria> // Interfaz de bebidas. clase Bebida { público : virtual void bebida () = 0 ; virtual ~ Bebida () = predeterminado ; }; // Bebidas que se pueden decorar. clase Café : público Bebida { público : virtual void bebida () override { std :: cout << "Bebiendo Café" ; } }; clase Soda : pública Bebida { pública : virtual void bebida () anular { std :: cout << "Bebiendo Soda" ; } }; clase BeverageDecorator : público Beverage { público : BeverageDecorator () = eliminar ; BeverageDecorator ( std :: unique_ptr < Beverage > componente_ ) : componente ( std :: move ( componente_ )) {} virtual void bebida () = 0 ; protegido : void callComponentDrink () { if ( componente ) { componente -> bebida (); } } privado : std :: unique_ptr < Bebida > componente ; }; clase Leche : público BeverageDecorator { público : Leche ( std :: unique_ptr < Bebida > componente_ , float porcentaje_ ) : BebidaDecorator ( std :: move ( componente_ )) , porcentaje ( porcentaje_ ) { } virtual void beber () anular { callComponentDrink (); std :: cout << ", con leche de riqueza " << porcentaje << "%" ; } privado : porcentaje flotante ; }; clase IceCubes : public BeverageDecorator { public : IceCubes ( std :: unique_ptr < Beverage > componente_ , int count_ ) : BeverageDecorator ( std :: move ( componente_ )) , count ( count_ ) { } virtual void bebida () anular { callComponentDrink (); std :: cout << ", con " << count << " cubitos de hielo" ; } privado : int cuenta ; }; clase Azúcar : público BeverageDecorator { público : Azúcar ( std :: unique_ptr < Bebida > componente_ , int cucharas_ ) : BeverageDecorator ( std :: move ( componente_ )) , cucharas ( cucharas_ ) { } virtual void bebida () anular { callComponentDrink (); std :: cout << ", con " << cucharas << " cucharadas de azúcar" ; } privado : int cucharas = 1 ; }; int main () { std :: unique_ptr < Bebida > soda = std :: make_unique < Soda > (); soda = std :: make_unique < CubitosDeHielo > ( std :: move ( soda ), 3 ); soda = std :: make_unique < Azúcar > ( std :: move ( soda ), 1 ); refresco -> bebida (); std :: cout << std :: endl ; std :: unique_ptr < Bebida > café = std :: make_unique < Café > (); café = std :: make_unique < CubitosDeHielo > ( std :: move ( café ), 16 ); café = std :: make_unique < Leche > ( std :: move ( café ), 3. ); café = std :: make_unique < Azúcar > ( std :: move ( café ), 2 ); café -> bebida (); devuelve 0 ; }
La salida del programa es como
Beber refresco , con 3 cubitos de hielo , con 1 cucharada de azúcar Beber café , con 16 cubitos de hielo , con leche de riqueza 3 % , con 2 cucharadas de azúcar
El ejemplo completo se puede probar en una página de godbolt.
Aquí se presentan dos opciones: primero, un decorador dinámico, componible en tiempo de ejecución (tiene problemas al llamar a funciones decoradas a menos que se utilice un proxy explícito) y un decorador que usa herencia de mixin.
#include <iostream> #include <cadena> estructura Forma { virtual ~ Forma () = predeterminado ; virtual std :: cadena GetName () const = 0 ; }; estructura Círculo : Forma { void Redimensionar ( float factor ) { radio *= factor ; } std :: string GetName () const override { return std :: string ( "Un círculo de radio" ) + std :: to_string ( radio ); } radio flotante = 10.0f ; }; struct ColoredShape : Forma { ColoredShape ( const std :: string & color , Forma * forma ) : color ( color ), forma ( forma ) {} std :: string GetName () const override { return forma -> GetName () + "que es coloreado" + color ; } std :: cadena color ; Forma * forma ; }; int main () { Círculo círculo ; FormaColoreada forma_coloreada ( " rojo" , & círculo ); std :: cout << forma_coloreada . ObtenerNombre () << std :: endl ; }
#include <memoria> #include <iostream> #include <cadena> estructura WebPage { virtual void display () = 0 ; virtual ~ WebPage () = predeterminado ; }; struct BasicWebPage : WebPage { std :: string html ; void display () override { std :: cout << "Página WEB básica" << std :: endl ; } }; estructura WebPageDecorator : WebPage { WebPageDecorator ( std :: unique_ptr < WebPage > webPage ) : _webPage ( std :: move ( webPage )) { } void display () override { _webPage -> display (); } privado : std :: unique_ptr < WebPage > _webPage ; }; estructura PáginaWebAutenticada : DecoradorDePáginaWeb { PáginaWebAutenticada ( std :: unique_ptr < PáginaWeb > PáginaWeb ) : DecoradorDePáginaWeb ( std :: mover ( PáginaWeb )) {} void authenticateUser () { std :: cout << "autenticación realizada" << std :: endl ; } void display () override { authenticateUser (); WebPageDecorator :: display (); } }; estructura AuthorizedWebPage : WebPageDecorator { AuthorizedWebPage ( std :: unique_ptr < WebPage > webPage ) : WebPageDecorator ( std :: move ( webPage )) {} void usuarioautorizado () { std :: cout << "autorizado hecho" << std :: endl ; } void display () override { usuarioautorizado (); WebPageDecorator :: display (); } }; int principal ( int argc , char * argv []) { std :: unique_ptr < PáginaWeb > miPagina = std :: make_unique < PáginaWebBásica > (); miPagina = std :: make_unique <PaginaWebAutorizada> ( std :: mover ( miPagina )); miPagina = std :: make_unique <PaginaWebAutenticada> ( std :: mover ( miPagina ) ) ; miPagina - > mostrar () ; std :: cout << std :: endl ; devolver 0 ; }
Este ejemplo demuestra una implementación de Decorator estático, lo cual es posible gracias a la capacidad de C++ de heredar del argumento de plantilla.
#include <iostream> #include <cadena> estructura Círculo { void Redimensionar ( float factor ) { radio *= factor ; } std :: string GetName () const { return std :: string ( "Un círculo de radio" ) + std :: to_string ( radio ); } radio flotante = 10.0f ; }; plantilla < typename T > struct ColoredShape : public T { ColoredShape ( const std :: string & color ) : color ( color ) {} std :: string GetName () const { return T :: GetName () + "que es de color" + color ; } std :: cadena color ; }; int main () { FormaColoreada < Círculo > círculo_rojo ( "rojo" ); std :: cout << círculo_rojo . ObtenerNombre () << std :: endl ; círculo_rojo . Redimensionar ( 1.5f ); std :: cout << círculo_rojo . ObtenerNombre () << std :: endl ; }
El siguiente ejemplo de Java ilustra el uso de decoradores utilizando el escenario de ventana/desplazamiento.
// La clase de interfaz de la ventana public interface Window { void draw (); // Dibuja la cadena Window getDescription (); // Devuelve una descripción de la ventana } // Implementación de una ventana simple sin ninguna barra de desplazamiento clase SimpleWindow implementa Window { @Override public void draw () { // Dibujar ventana } @Override public String getDescription () { return "ventana simple" ; } }
Las siguientes clases contienen los decoradores para todas Window
las clases, incluidas las propias clases decoradoras.
// clase decoradora abstracta - tenga en cuenta que implementa Window clase abstracta WindowDecorator implementa Window { private final Window windowToBeDecorated ; // la ventana que se está decorando public WindowDecorator ( Window ventanaASerDecorado ) { this . ventanaASerDecorado = ventanaASerDecorado ; } @Override public void draw () { ventanaASerDecorado . draw (); //Delegación } @Override public String getDescription () { return ventanaASerDecorado . getDescription (); //Delegación } } // El primer decorador concreto que agrega la funcionalidad de barra de desplazamiento vertical class VerticalScrollBarDecorator extends WindowDecorator { public VerticalScrollBarDecorator ( Window windowToBeDecorated ) { super ( windowToBeDecorated ); } @Override public void draw () { super.draw (); drawVerticalScrollBar ( ) ; } private void drawVerticalScrollBar () { // Dibuja la barra de desplazamiento vertical } @Override public String getDescription () { return super . getDescription () + ", incluidas las barras de desplazamiento verticales" ; } } // El segundo decorador concreto que agrega la funcionalidad de barra de desplazamiento horizontal class HorizontalScrollBarDecorator extends WindowDecorator { public HorizontalScrollBarDecorator ( Window windowToBeDecorated ) { super ( windowToBeDecorated ); } @Override public void draw () { super.draw (); drawHorizontalScrollBar ( ) ; } private void drawHorizontalScrollBar () { // Dibuja la barra de desplazamiento horizontal } @Override public String getDescription () { return super . getDescription () + ", incluidas las barras de desplazamiento horizontales" ; } }
Aquí hay un programa de prueba que crea una Window
instancia completamente decorada (es decir, con barras de desplazamiento verticales y horizontales) e imprime su descripción:
clase pública DecoratedWindowTest { pública estática void main ( String [] args ) { // Crea una ventana decorada con barras de desplazamiento horizontales y verticales Ventana decoradaWindow = new HorizontalScrollBarDecorator ( new VerticalScrollBarDecorator ( new SimpleWindow ())); // Imprimir la descripción de la ventana System . out . println ( decorativeWindow . getDescription ()); } }
El resultado de este programa es "ventana simple, incluidas las barras de desplazamiento verticales, incluidas las barras de desplazamiento horizontales". Observe cómo el getDescription
método de los dos decoradores primero recupera la Window
descripción del decorado y lo decora con un sufijo.
A continuación se muestra la clase de prueba JUnit para el desarrollo impulsado por pruebas
importar org.junit.Assert.assertEquals estático ; importar org.junit.Test ; public class WindowDecoratorTest { @Test public void testWindowDecoratorTest () { Ventana decoradaWindow = new HorizontalScrollBarDecorator ( new VerticalScrollBarDecorator ( new SimpleWindow ())); // afirmar que la descripción de hecho incluye barras de desplazamiento horizontales y verticales assertEquals ( "ventana simple, incluidas las barras de desplazamiento verticales, incluidas las barras de desplazamiento horizontales" , decoradaWindow . getDescription ()); } }
El siguiente ejemplo de Java ilustra el uso de decoradores en un escenario de preparación de café. En este ejemplo, el escenario solo incluye costos e ingredientes.
// La interfaz Coffee define la funcionalidad de Coffee implementada por el decorador public interface Coffee { public double getCost (); // Devuelve el costo del café public String getIngredients (); // Devuelve los ingredientes del café } // Extensión de un café simple sin ningún ingrediente extra public class SimpleCoffee implements Coffee { @Override public double getCost () { return 1 ; } @Override public String getIngredients () { return "Café" ; } }
Las siguientes clases contienen los decoradores para todas las clases de café , incluidas las clases de decoradores en sí.
// Clase decoradora abstracta: tenga en cuenta que implementa la interfaz Coffee clase abstracta pública CoffeeDecorator implementa Coffee { private final Coffee decoradoCoffee ; public CoffeeDecorator ( Café c ) { this.decoratedCoffee = c ; } @Override public double getCost () { // Implementando los métodos de la interfaz return decorativeCoffee.getCost ( ) ; } @Override public String obtenerIngredientes () { return Cafédecorado.obtenerIngredientes ( ) ; } } // Decorator WithMilk mezcla leche con café. // Tenga en cuenta que extiende CoffeeDecorator. class WithMilk extends CoffeeDecorator { public WithMilk ( Coffee c ) { super ( c ); } @Override public double getCost () { // Anulando los métodos definidos en la superclase abstracta return super . getCost () + 0.5 ; } @Override public String obtenerIngredientes () { return super.getIngredientes ( ) + " , Leche" ; } } // Decorator WithSprinkles mezcla chispas sobre el café. // Tenga en cuenta que extiende CoffeeDecorator. class WithSprinkles extiende CoffeeDecorator { public WithSprinkles ( Coffee c ) { super ( c ); } @Override public double obtenerCost ( ) { devolver super.getCost ( ) + 0.2 ; } @Override public String obtenerIngredientes () { return super.getIngredientes () + " , Chispas" ; } }
Aquí hay un programa de prueba que crea una instancia de café que está completamente decorada (con leche y chispas), calcula el costo del café e imprime sus ingredientes:
clase pública Main { public static void printInfo ( Café c ) { System.out.println ( " Costo: " + c.getCost ( ) + " ; Ingredientes : " + c.getIngredients ( ) ) ; } public static void main ( String [] args ) { Café c = new SimpleCoffee (); printInfo ( c ); c = nuevo WithMilk ( c ); printInfo ( c ); c = nuevo WithSprinkles ( c ); printInfo ( c ); } }
El resultado de este programa se muestra a continuación:
Costo: 1.0; Ingredientes: CaféCosto: 1.5; Ingredientes: Café, LecheCosto: 1.7; Ingredientes: Café, Leche, Chispas
clase abstracta Componente { protegido $datos ; protegido $valor ; función pública abstracta getData (); función pública abstracta getValue (); } clase ConcreteComponent extiende Component { función pública __construct () { $this -> valor = 1000 ; $this -> datos = "Componente de hormigón: \t { $this -> valor } \n " ; } función pública getData () { devolver $this -> datos ; } función pública getValue () { return $this -> valor ; } } La clase abstracta Decorator extiende Component { }clase ConcreteDecorator1 extiende Decorator { función pública __construct ( Componente $data ) { $this -> valor = 500 ; $this -> datos = $data ; } función pública obtenerDatos () { devolver $this -> datos -> obtenerDatos () . "Decorador de hormigón 1: \t { $this -> valor } \n " ; } función pública obtenerValor () { devolver $this -> valor + $this -> datos -> obtenerValor (); } }clase ConcreteDecorator2 extiende Decorator { función pública __construct ( Componente $data ) { $this -> valor = 500 ; $this -> datos = $data ; } función pública obtenerDatos () { devolver $this -> datos -> obtenerDatos () . "Decorador de hormigón 2: \t { $this -> valor } \n " ; } función pública obtenerValor () { devolver $this -> valor + $this -> datos -> obtenerValor (); } }clase Cliente { privado $componente ; función pública __construct () { $this -> componente = new ConcreteComponent (); $this -> componente = $this -> wrapComponent ( $this -> componente ); echo $this -> componente -> obtenerDatos (); echo "Cliente: \t\t\t " ; echo $this -> componente -> obtenerValor (); } función privada wrapComponent ( Componente $componente ) { $componente1 = nuevo ConcreteDecorator1 ( $componente ); $componente2 = nuevo ConcreteDecorator2 ( $componente1 ); devolver $componente2 ; } }$cliente = nuevo Cliente ();// Resultado: #quanton81//Componente de hormigón: 1000 //Decorador de hormigón 1: 500 //Decorador de hormigón 2: 500 //Cliente: 2000
El siguiente ejemplo de Python, tomado de Python Wiki - DecoratorPattern, nos muestra cómo canalizar decoradores para agregar dinámicamente muchos comportamientos en un objeto:
""" Decoradores demostrados en un mundo de una cuadrícula de 10x10 con valores de 0 a 255. """importar aleatoriodef s32_to_u16 ( x ): si x < 0 : signo = 0xF000 de lo contrario : signo = 0 inferior = x & 0x00007FFF devolver inferior | signodef semilla_de_xy ( x , y ): devuelve s32_a_u16 ( x ) | ( s32_a_u16 ( y ) << 16 )clase RandomSquare : def __init__ ( s , modificador_semilla ): s . modificador_semilla = modificador_semilla def get ( s , x , y ) : semilla = semilla_de_xy ( x , y ) ^ s.semilla_modificador aleatorio.semilla ( semilla ) return aleatorio.randint ( 0,255 ) clase DataSquare : def __init __ ( s , valor_inicial = None ) : s.data = [ valor_inicial ] * 10 * 10 def get ( s , x , y ): return s . data [( y * 10 ) + x ] # sí: todos son 10x10 def conjunto ( s , x , y , u ): s . datos [( y * 10 ) + x ] = uclase CacheDecorator : def __init__ ( s , decorado ): s . decorado = decorado s . cache = DataSquare () def obtener ( s , x , y ): si s . cache . get ( x , y ) == Ninguno : s . cache . set ( x , y , s . decorado . get ( x , y )) devuelve s . cache . get ( x , y )clase MaxDecorator : def __init__ ( s , decorado , max ): s . decorado = decorado s . max = max def get ( s , x , y ) : if s.decorated.get ( x , y ) > s.max : return s.max return s.decorated.get ( x , y ) clase MinDecorator : def __init__ ( s , decorado , min ): s . decorado = decorado s . min = min def get ( s , x , y ) : if s.decorated.get ( x , y ) < s.min : return s.min return s.decorated.get ( x , y ) clase VisibilityDecorator : def __init__ ( s , decorado ): s . decorado = decorado def get ( s , x , y ): devuelve s . decorado . get ( x , y ) def draw ( s ): para y en rango ( 10 ): para x en rango ( 10 ): imprimir " %3d " % s . get ( x , y ), imprimir# Ahora, crea una tubería de decoradores:cuadrado_aleatorio = Cuadrado_aleatorio ( 635 ) caché_aleatorio = Decorador_de_caché ( cuadrado_aleatorio ) filtrado_máximo = Decorador_máximo ( caché_aleatorio , 200 ) filtrado_mínimo = Decorador_mínimo ( filtrado_máximo , 100 ) final = Decorador_de_visibilidad ( filtrado_mínimo )sorteo final ( )
Nota:
El patrón Decorator (o una implementación de este patrón de diseño en Python, como el ejemplo anterior) no debe confundirse con los Decorators de Python , una característica del lenguaje Python. Son cosas diferentes.
En segundo lugar, en la Wiki de Python:
El patrón Decorator es un patrón descrito en el libro Design Patterns Book. Es una forma de modificar aparentemente el comportamiento de un objeto, encerrándolo dentro de un objeto decorativo con una interfaz similar. No debe confundirse con los Decorators de Python, que es una característica del lenguaje para modificar dinámicamente una función o clase. [8]
Clase abstracta Café Definición abstracta Costo Definición abstracta Ingredientes Fin # Extensión de una clase de café simple SimpleCoffee < Coffee def cost 1 . 0 end def ingredientes "Café" fin fin # Clase decoradora abstracta CoffeeDecorator < Coffee protected getter decorative_coffee : Coffee def inicializar ( @decorated_coffee ) fin def costo café decorado . costo fin def ingredientes café decorado . ingredientes fin fin clase WithMilk < CoffeeDecorator def costo super + 0 . 5 fin def ingredientes super + ", leche" fin fin clase WithSprinkles < CoffeeDecorator def costo super + 0 . 2 fin def ingredientes super + ", chispas" fin fin clase Programa def print ( café : Café ) pone " Costo: #{ café.costo } ; Ingredientes : # { café.ingredientes } " fin def inicializar café = SimpleCoffee . new print ( café ) café = ConLeche . nuevo ( café ) imprimir ( café ) café = WithSprinkles . nuevo ( café ) imprimir ( café ) fin fin Programa . nuevo
Producción:
Costo: 1.0; Ingredientes: CaféCosto: 1.5; Ingredientes: Café, LecheCosto: 1.7; Ingredientes: Café, Leche, Chispas
espacio de nombres WikiDesignPatterns ; interfaz pública IBike { cadena GetDetails (); doble GetPrice (); } clase pública AluminiumBike : IBike { pública doble GetPrice () => 100.0 ; public string GetDetails () => "Bicicleta de aluminio" ; } clase pública CarbonBike : IBike { pública doble GetPrice () => 1000.0 ; cadena pública GetDetails () => "Carbono" ; } clase abstracta pública BikeAccessories : IBike { privada de solo lectura IBike _bike ; AccesoriosBici públicos ( IBike bicicleta ) { _bike = bicicleta ; } público virtual doble ObtenerPrecio () => _bike.ObtenerPrecio ( ) ; cadena virtual pública GetDetails () => _bike.GetDetails ( ) ; } clase pública SecurityPackage : BikeAccessories { paquete público SecurityPackage ( IBike bicicleta ): base ( bicicleta ) { } cadena de anulación pública GetDetails () => base . GetDetails () + " + Paquete de seguridad" ; público anular doble ObtenerPrecio ( ) => base.ObtenerPrecio ( ) + 1 ; } clase pública SportPackage : BikeAccessories { clase pública SportPackage ( IBike bicicleta ) : base ( bicicleta ) { } cadena de anulación pública GetDetails () => base . GetDetails () + " + Paquete deportivo" ; público anular doble ObtenerPrecio ( ) => base.ObtenerPrecio ( ) + 10 ; } clase pública BikeShop { pública estática void UpgradeBike () { var basicBike = new AluminiumBike (); BikeAccessories actualizado = nuevo SportPackage ( basicBike ); actualizado = nuevo SecurityPackage ( actualizado ); Consola .WriteLine ( $"Bicicleta: '{upgraded.GetDetails()}' Costo: {upgraded.GetPrice()}" ) ; } }
Producción:
Bicicleta: 'Bicicleta de Aluminio + Paquete Deportivo + Paquete de Seguridad' Coste: 111
clase AbstractCoffee def print puts "Costo: #{ costo } ; Ingredientes: #{ ingredientes } " fin fin clase SimpleCoffee < AbstractCoffee def costo 1 . 0 fin def ingredientes "Café" fin fin clase WithMilk < SimpleDelegator def costo __getobj__ . costo + 0 . 5 fin def ingredientes __getobj__ . ingredientes + ", Leche" fin fin clase WithSprinkles < SimpleDelegator def costo __getobj__ . costo + 0 . 2 fin def ingredientes __getobj__ . ingredientes + ", Chispas" fin fin café = SimpleCoffee . nuevo café . imprimir café = ConLeche . nuevo ( café ) café . imprimir café = WithSprinkles . nuevo ( café ) café . imprimir
Producción:
Costo: 1.0; Ingredientes: CaféCosto: 1.5; Ingredientes: Café, LecheCosto: 1.7; Ingredientes: Café, Leche, Chispas
{{cite book}}
: CS1 maint: multiple names: authors list (link)