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

El patrón Builder es un patrón de diseño creativo que maneja la construcción de objetos complejos paso a paso (o ladrillo por ladrillo). Es probablemente el patrón más fácil de detectar si el código existente necesita dicho diseño. El patrón Builder permite la creación de diferentes representaciones de un objeto utilizando el mismo código de construcción.

El patrón del generador se clasifica en los patrones de diseño de creación, que se trata de instanciación de clase / objeto. Más precisamente, cómo usar efectivamente la herencia (patrones de creación de clase) o la delegación (patrones de creación de objetos). [por Patrones de diseño explicados simplemente]

Ejemplo: comencemos con un ejemplo sobre mi tema favorito. ¡¡Comida!! Más precisamente, el ejemplo será sobre pizza. La pizza se separa en tres capas, la masa, la salsa y los ingredientes. Ahora, preguntémonos, ¿cómo "construiríamos" un objeto de pizza seleccionando entre varios ingredientes y una variedad de salsas?

Preguntas antes de diseñar una solución a este problema:

  • ¿Cómo se vería el constructor de la clase Pizza?
  • ¿Deberíamos tener múltiples constructores de sobrecarga con parámetros para todas las combinaciones como salsas, coberturas más salsas, etc.? ¿Qué pasa si también tenemos diferentes tipos de masa?
  • ¿Qué pasa si más adelante decidimos agregar queso o diferentes tipos de queso? ¿Sería fácil de implementar? ¿Qué pasa con las dependencias del código existente?
  • ¿Deberíamos tener un constructor predeterminado que cree una "pizza simple"? ¿No obligaría eso al cliente a llamar a una serie de emisores en consecuencia? ¿Es esto seguro? ¿Qué pasa si el cliente se olvida y luego le enviamos una pizza simple al cliente?

La solución a todas estas preguntas es el patrón de construcción. Necesitamos un "constructor" para construir una pizza paso a paso. Al mismo tiempo, el cliente estará seguro de "pedir" una pizza específica y que el constructor la construya para él.

Paso 1 - Palabras clave

  1. Representación: Un objeto puede tener diferentes representaciones. Por ejemplo, una pizza puede tener coberturas de mozzarella de tomate y otra representación de pizza sería con champiñones y jamón de Parma.
  2. Código de construcción: los objetos se pueden construir de diferentes maneras, por ejemplo, utilizando constructores. Los constructores pueden sobrecargarse, tienen el mismo nombre (nombre de la clase) pero un número / tipo diferente de argumentos. Dependiendo del número y tipo de argumentos pasados, se llama al constructor específico.

Es fácil caer en la trampa de tener una clase de numerosos constructores donde cada uno toma un número diferente de parámetros. El desarrollador se ve obligado a crear una instancia de la clase con la combinación correcta de parámetros en cada situación. Este problema tiene un nombre, es un antipatrón popular llamado constructor telescópico y el patrón constructor es la solución para solucionarlo. Simplifiquemos en exceso el patrón de Builder diciendo sin rodeos:

La intención principal del patrón Builder es tener el número mínimo de constructores de sobrecarga para soportar la construcción de varias representaciones de un objeto.

Paso 2 - Diagrama (por ejemplo)

Sigamos con el ejemplo de la pizza. En resumen, tenemos la clase de pizza, el cocinero y los constructores de concreto que heredan del constructor abstracto. Explicaremos el diagrama de abajo hacia arriba:

  • Pizza_Product: esta clase es la pizza real. Está representado por tres atributos: (1) Masa, (2) salsa y (3) coberturas.
  • ConcreteBuilder: cada constructor de concreto es responsable de una representación específica. En este ejemplo, tenemos Margherita y Spicy Pizza. Ambos constructores de concreto son responsables de "construir" su propia representación de pizza basada en los tres atributos enumerados anteriormente.
  • AbstractBuilder: contiene una variable miembro de pizza y los constructores concretos heredan de ella.
  • Cook_Director: en este ejemplo, el director es el cocinero real. La clase es responsable de iniciar la construcción de una representación dada al juntar las piezas de manera que el constructor siga y se adapte de acuerdo a las necesidades del director.

Paso 3 - 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).

Producto:
Esta es la clase de pizza. Es una clase simple con tres setters y un método taste () que imprime todos los ingredientes.

#include 
#include  // unique_ptr
usando el espacio de nombres estándar;
clase Pizza_Product
{
público:
 vacío setDough (cadena y masa const) {m_dough = masa; }
 vacío setSauce (const string & sauce) {m_sauce = sauce; }
 void setTopping (const string & topping) {m_topping = topping; }
sabor vacío () const
{
  cout << "Pizza con" << m_dough << "masa"
       << m_sauce << "salsa y"
       << m_topping << "topping. Mmmmmmm". << endl;
}
privado:
 string m_dough;
 string m_sauce;
 string m_topping;
};

Generador de resumen:
El generador abstracto es una interfaz que contiene un objeto de pizza. Tiene un captador que devuelve el objeto de pizza y un método para crear una instancia del objeto de pizza. También declara los tres métodos de construcción que serán implementados por los constructores de concreto más abajo.

clase Pizza_Builder
{
público:
  virtual ~ Pizza_Builder () {};
  Pizza_Product * getPizza () {return m_pizza.release (); }
  void createNewPizzaProduct ()
  {
    m_pizza = make_unique  ();
  }
  virtual void buildDough () = 0;
  vacío virtual buildSauce () = 0;
  virtual void buildTop () = 0;
protegido:
  unique_ptr  m_pizza;
};

Constructores de Concreto:
Dos ejemplos de constructores de hormigón y dos representaciones de una pizza. Ambos implementan sus propios métodos de compilación utilizando el objeto m_pizza de la clase padre (Generador abstracto)

clase Margherita_ConcreteBuilder: public Pizza_Builder
{
público:
 virtual void buildDough () {m_pizza-> setDough ("cross"); }
 virtual void buildSauce () {m_pizza-> setSauce ("tomate"); }
 virtual void buildTop () {m_pizza-> setTopping ("mozzarela + basil"); }
};
clase Spicy_ConcreteBuilder: public Pizza_Builder
{
público:
 virtual void buildDough () {m_pizza-> setDough ("pan baked"); }
 vacío virtual buildSauce () {m_pizza-> setSauce ("tomate + chile"); }
 virtual void buildTop () {m_pizza-> setTopping ("pepperoni + salami"); }
};

Director:
Esta clase pone todo junto. Tiene una variable Pizza_Builder. Utiliza makePizza () para recibir un constructor concreto como parámetro. Luego llama a las operaciones de construcción que funcionan para ambas representaciones en consecuencia. Por lo tanto, logramos el propósito de tener un código de construcción para representar diferentes tipos de pizzas. El método tastePizza () es imprimir el contenido de una pizza.

clase Cook_Director
{
público:
 vacío tastePizza () {m_pizzaBuilder-> getPizza () -> taste (); }
anular makePizza (Pizza_Builder * pb)
 {
   m_pizzaBuilder = pb;
   m_pizzaBuilder-> createNewPizzaProduct ();
   m_pizzaBuilder-> buildDough ();
   m_pizzaBuilder-> buildSauce ();
   m_pizzaBuilder-> buildTop ();
 }
privado:
 Pizza_Builder * m_pizzaBuilder;
};

Principal (cliente):
 El método principal funciona como el cliente (igual que las guías anteriores). Es muy fácil para el cliente poder construir diferentes representaciones de una pizza. Necesitamos al director, y luego, simplemente pasando dos constructores de concreto diferentes como parámetro de themakePizza, podremos clasificar dos tipos diferentes de pizzas.

int main ()
{
  Cook_Director cook;
  Margherita_ConcreteBuilder margheritaBuilder;
  Spicy_ConcreteBuilder spicyPizzaBuilder;
  cook.makePizza (& margheritaBuilder);
  cook.tastePizza ();
  cook.makePizza (& spicyPizzaBuilder);
  cook.tastePizza ();
}
// Salida
// Pizza con masa cruzada, salsa de tomate y mozzarela + cobertura de albahaca. // Mmmmmmm.
// Pizza con masa horneada, salsa de tomate + chile y
// topping de pepperoni + salami. Mmmmmmm

Resumamos los beneficios de este patrón:

  • Hay muchas representaciones posibles pero solo una entrada común.
  • Tenemos un protocolo estándar para crear todas las representaciones posibles. Los pasos de este protocolo están claramente expuestos por una interfaz de Builder.
  • Hay una clase concreta derivada para cada representación objetivo.
  • Es fácil para un desarrollador agregar una nueva representación autosuficiente e independiente (de una Pizza) sin temor a romper otra cosa.
  • El cliente simplemente puede registrar el ConcreteBuilder al Director y obtener la representación esperada.

El próximo blog será una guía rápida del patrón de diseño de Decorator. Es un patrón estructural que es imprescindible para su repositorio de conocimientos. 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, hágamelo saber 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.