Patrones de diseño: una guía rápida para el patrón de decorador.

El patrón Decorator es un patrón estructural que le permite adjuntar funcionalidades adicionales a un objeto dinámicamente. En otras palabras, el cliente tiene la libertad de crear un objeto y luego extenderlo agregando una variedad de "características". Una buena analogía para simplificar este patrón es: "Envolver un regalo, ponerlo en una caja y envolver la caja".

El patrón Decorador se clasifica entre los patrones de diseño estructural que tratan sobre la composición de Clase y Objeto. Los patrones estructurales de creación de clases utilizan la herencia para componer interfaces. Los patrones de objetos estructurales definen formas de componer objetos para obtener una nueva funcionalidad. [por Patrones de diseño explicados simplemente]

Una mochila en el ejemplo perfecto de un patrón Decorador:

  • Hoy en día, las mochilas pueden tener una variedad de características. Las características pueden variar desde algo tan simple como una ranura para computadora portátil, bolsillos laterales o incluso un banco de energía para la carga USB.
  • El objetivo principal del patrón Decorator es permitir que el cliente agregue las características necesarias, de forma dinámica, segura y de la manera más fácil posible. Debe ser tan sencillo y ordenado como colocar la mochila en la imagen.
  • El fragmento de código a continuación está escrito a propósito, ya que converge muy bien con la cita que mencionamos anteriormente: "Envolver un regalo, ponerlo en una caja y envolver la caja". Montamos una mochila a la que agregamos un cargador USB al que agregamos una ranura para computadora portátil a la que agregamos ...
int main // el cliente
{
  IBackpack * bp = new LaptopSlot (nuevo UsbCharge (...))));
  retorno 1;
}

Paso 1 - Palabras clave

Definir palabras clave es la receta secreta en esta serie de guías rápidas. Este método me ayudó a comprender realmente los patrones de diseño, codificarlos en mi mente y comprender las diferencias entre otros patrones de diseño.

  • Flexibilidad: Queremos darle al cliente el poder y la flexibilidad para agregar dinámicamente cualquier característica a un componente / objeto, que podría considerarse valiosa.
  • Extender la funcionalidad: el título puede ser un poco engañoso, este patrón no se trata solo de "decorar" un determinado objeto, sino que se trata principalmente de extender su funcionalidad.

Paso 2 - Diagramas por ejemplo

Comencemos explicando el diagrama de abajo hacia arriba. El objetivo es "armar" una mochila y agregarle varias funcionalidades.

  • Decoradores de concreto: Tenemos tres ejemplos de estas clases que son responsables de extender una funcionalidad adicional cada uno: (1) LaptopSlot, (2) USBCharge, (3) WaterBottle. Mantienen la implementación de la funcionalidad y pueden "ensamblar" una mochila en consecuencia. Tenga en cuenta que tienen un constructor que recibe un decorador como parámetro.
  • Decorador: esta clase deriva las clases anteriores y hereda del componente que es IBackpack. El decorador también tiene una instancia de IBackpack. Después de instanciar IBackpackis, se está utilizando dentro del método assemble ().
  • Componente concreto: esta clase es la pieza más importante del rompecabezas, ya que es la clave para unir todo. Es el componente (mochila) que está en la forma más simple que podría obtener. Por ejemplo, una mochila simple consta solo de las "correas para los hombros y el compartimento principal". La mochila simple funciona como el comienzo de una cadena. Se pasa al constructor del decorador y se pasa al constructor de un decorador de concreto (para agregar funcionalidades específicas, es decir, una ranura para computadora portátil) que se puede pasar a otro constructor de un decorador de concreto y otro y así sucesivamente.
  • Componente: es una vista abstracta del objeto que queremos decorar. Podemos tener diferentes componentes concretos heredando de él. En este ejemplo, podríamos estar separando el "PlainBackpack" como "OfficeBackpack", "CampingBackpack", "HikingBackpack", pero elegimos mantenerlo simple.

Paso 23— Código por ejemplo

Sugeriría copiar el código clase por clase de mi repositorio git "Andreas Poyias" o los fragmentos a continuación (en el orden proporcionado) y pegarlo en cualquiera de los editores C ++ en línea disponibles como c ++ shell, jdoodle, onlineGDB y ejecutarlo para observar la salida. Luego lea los comentarios o la descripción a continuación. Tómese el tiempo de leerlo a fondo (eso significa un minuto, ni menos ni más).

IBackpack:
El siguiente fragmento de código es una herencia simple, IBackpack es heredada por PlainBackpackclass. Todas las clases derivadas deben implementar assemble (), ya que es una función virtual pura = 0 ;. Una mochila simple solo tiene tirantes y el compartimento principal.

#include 
usando el espacio de nombres estándar;

clase IBackpack
{
público:
  montaje vacío virtual () = 0;
  virtual ~ IBackpack () {}
};

clase PlainBackpack: público IBackpack
{
público:
  montaje vacío virtual () {cout << "\ n ShoulderStraps and mainCompartment";}
};

Decorador de mochila:
El fragmento anterior es responsable de la construcción de una mochila simple. Ahora, decorémoslo usando el BackpackDecorator. El decorador hereda de theBackpack, lo que significa que debe implementar el método thesesemble (). También contiene un objeto IBackpack que se usa para delegar la implementación del método assemble () dependiendo del tipo de m_decorator.

class BackpackDecorator: IBackpack público
{
público:
  BackpackDecorator (decorador IBackpack *): m_Decorator (decorador) {}
  
  montaje vacío virtual ()
  {
    m_Decorator-> assemble ();
  }
privado:
  IBackpack * m_Decorator;
};

Decoradores de hormigón:
El fragmento a continuación muestra tres decoradores diferentes que se derivan de la clase BackpackDecorator que hereda de IBackpack. En este ejemplo, todos son idénticos, aparte de la implementación de ensamblar (). El primero agrega una ranura para computadora portátil, el segundo agrega una carga UBC y el tercero agrega una botella de agua.

clase WithLaptopSlot: public BackpackDecorator
{
público:
  WithLaptopSlot (IBackpack * dcrator): BackpackDecorator (dcrator) {}
  montaje vacío virtual ()
  {
    BackpackDecorator :: assemble ();
    cout << "+ LaptopSlot";
  }
};
 
clase WithUSBCharge: public BackpackDecorator
{
público:
    WithUSBCharge (IBackpack * dcrator): BackpackDecorator (dcrator) {}
    montaje vacío virtual ()
    {
        BackpackDecorator :: assemble ();
        cout << "+ USBCharge";
    }
};
 
clase WithWaterBottle: public BackpackDecorator
{
público:
    WithWaterBottle (IBackpack * dcrator): BackpackDecorator (dcrator) {}
    montaje vacío virtual ()
    {
        BackpackDecorator :: assemble ();
        cout << "+ WaterBottle";
    }
};

Principal (Cliente):
El método principal funciona como el cliente (igual que las guías anteriores). Finalmente podemos armar todas las piezas del rompecabezas. Pero antes de hacerlo, recordemos lo que se mencionó en el primer párrafo del blog "Envolver un regalo, ponerlo en una caja y envolver la caja". Para entender esto, debemos leer la construcción de la mochila (en el fragmento a continuación) en orden inverso:

  1. Crea una mochila simple.
  2. Pásalo al BackpackDecorator.
  3. Lo que lo pasa para decorarlo con una ranura para computadora portátil.
  4. A su vez, se pasa a decorarse con una carga USB.
  5. Finalmente, la "caja" se "envuelve" con una botella de agua.

También es importante observar el orden de impresión mientras se llama a assemble (). El orden está relacionado con cómo se inicializaron los constructores. Comienza de nuevo desde la mochila simple y está decorado hasta el final para llegar a la botella de agua.

int main ()
{
  IBackpack * pBackpack =
   nuevo WithWaterBottle (// 5
    nuevo WithUSBCharge (// 4
     nuevo WithLaptopSlot (// 3
      nuevo BackpackDecorator (// 2
       nuevo PlainBackpack ())))); // 1

  pBackpack-> assemble ();
  eliminar pBackpack;

  devuelve 0;
}
// Salida
// ShoulderStraps y mainCompartment + LaptopSlot + USBCharge
// + botella de agua

La simplicidad ofrecida al cliente es uno de los mayores beneficios.

  • El cliente tiene el poder de ensamblar dinámicamente una mochila con cualquier característica disponible.
  • Es simple, fácil y seguro.
  • El cliente no necesita mezclarse con el código.
  • Agregar un decorador "derivado" adicional es simple e independiente de otros decoradores derivados.

No olvides dar me gusta / aplaudir mi blog y seguir mi cuenta. Esto es para darme la satisfacción de haber ayudado a otros desarrolladores y empujarme a seguir escribiendo. Si hay un patrón de diseño específico sobre el que le gustaría aprender, avíseme en los comentarios a continuación para que pueda proporcionárselo en las próximas semanas.

Otras guías rápidas sobre patrones de diseño:

  1. Patrones de diseño: una guía rápida de Abstract Factory.
  2. Patrones de diseño: una guía rápida para el patrón de puente.
  3. Patrones de diseño: una guía rápida para el patrón de construcción.
  4. Patrones de diseño: una guía rápida para el patrón de decorador.
  5. Patrones de diseño: una guía rápida para el patrón de fachada.
  6. Patrones de diseño: una guía rápida para el patrón de observador.
  7. Patrones de diseño: una guía rápida del patrón Singleton.