English Русский 中文 Deutsch 日本語 Português
preview
Patrones de diseño en MQL5 (Parte I): Patrones de creación (Creational Patterns)

Patrones de diseño en MQL5 (Parte I): Patrones de creación (Creational Patterns)

MetaTrader 5Trading | 11 abril 2024, 16:54
250 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Introducción

En esencia, la programación se basa en la resolución de problemas, y los propios problemas pueden darse en distintas partes de un mismo programa o incluso en distintas aplicaciones. Vamos a imaginar que cada vez que nos enfrentamos al mismo problema, estamos repitiendo los mismos pasos y dedicando tiempo a resolverlo una y otra vez. De hecho, con este planteamiento, reinventaremos la rueda cada vez. Este planteamiento no solo no resulta útil, sino que es perjudicial, pues consume mucho tiempo y esfuerzo. Esto nos lleva a preguntarnos: ¿existe alguna forma de ahorrar este tiempo ineficaz, así como nuestras energías?

La respuesta es que sí, existen formas, o más bien patrones, que pueden utilizarse para resolver problemas concretos. Se trata de "patrones de diseño" (Design Patterns). Los patrones de diseño apoyan el concepto de programación orientada a objetos DRY ("no te repitas"), lo cual significa que no tendremos que reinventar la rueda cada vez. Si hay una tarea y existe un patrón que puede usarse para resolverla eficazmente, bastará con utilizar ese patrón y ahorrar tiempo y esfuerzo.

En este artículo repasaremos juntos los patrones de diseño y sus tipos, además de intentar crear una guía práctica sobre su uso a la hora de escribir programas en MQL5.

En el presente artículo, analizaremos los siguientes temas:

Los patrones de diseño son un tema muy importante para todos los programadores que desean mejorar su productividad y resultados. Hoy trataremos de repasar los conceptos de este interesante e importante tema de la forma más sencilla posible, para que todo resulte comprensible a los principiantes. También analizaremos ejemplos sencillos de uso de patrones en MQL5. A su manera, será una guía práctica.

Intentaremos abarcar cada tipo lo mejor posible y, al hacerlo, consideraremos todos los detalles necesarios cuya ausencia pueda dificultar la comprensión. Esperamos que el artículo le resulte informativo y que le aporte algo nuevo.

Tenga en cuenta que para comprender el material deberá conocer el tema del diseño orientado a objetos. Si no está familiarizado con la programación orientada a objetos, le recomiendo que lea el artículo "Programación orientada a objetos (POO) en MQL5".

¡Atención! El contenido de este artículo se facilita "tal cual": tiene fines exclusivamente ilustrativos y no constituye una recomendación comercial. El artículo no garantiza resultado alguno. El lector deberá entender que cualquier cosa que ponga en práctica usando como base el presente artículo, se realizará por su propia cuenta y riesgo: el autor no garantiza ningún resultado.


Definición de patrones de diseño

En esta parte, aprenderemos algo más sobre la definición de patrones de diseño. Estos son patrones que pueden utilizarse para resolver problemas específicos, descritos y recurrentes en el desarrollo de software. Cada patrón está diseñado para un problema específico orientado a objetos. Estos patrones podrán implementarse fácilmente en lenguajes básicos orientados a objetos. Cuando hablamos de un patrón de diseño, solemos mencionar cuatro elementos importantes:

  • El nombre - el nombre del patrón para resolver el problema específico y descrito.
  • El problema - el problema descrito, recurrente y específico, con el que nos podemos encontrar.
  • La solución - la solución descrita para el problema específico descrito.
  • Las consecuencias - los resultados de la aplicación de un patrón para resolver un problema.

Según la finalidad del uso, podremos distinguir tres tipos de patrones:

  • De creación - estos patrones ponen de relieve el proceso de creación de un sistema que deja de depender del método de formación y composición del objeto.
  • Estructurales - se encargan de utilizar los objetos creados para formar estructuras mayores.
  • Conductuales - definen algoritmos y métodos para implementar la interacción entre objetos.

Patrones de creación (Creational Patterns)

En este artículo hablamos de los patrones de diseño generativo y de cómo pueden ayudarnos a escribir programas. Así, el patrón de creación permite implementar un sistema independiente que ya no dependa del método de formación y composición de los objetos. El objetivo principal de estas patrones será ayudar al programador a crear aplicaciones eficientes con reutilización de código y extensibilidad. También permitirán probar programas con eficacia y escribir un código limpio.

Las clases del patrón de creación utilizan el concepto de herencia, lo que significa que pueden heredar propiedades y métodos de otras clases. Esto permitirá modificar el comportamiento de una clase, convirtiéndola en un ejemplar (objeto) que cumpla determinados requisitos. El objeto de patrón de creación delegará la tarea de creación de un nuevo ejemplar (ejemplarización) en otro objeto. Cuando un programa se centra en la composición de objetos más que en la herencia de clases, la creación de patrones adquiere mayor importancia.

Podemos decir que estos patrones tienen dos temas recurrentes:

  • Utilizan el concepto de encapsulación para conocer las clases específicas que puede usar el sistema.
  • Hacen que el método de creación de ejemplares de clase y su combinación quede oculto.

Los patrones de creación ofrecen flexibilidad en cuanto a qué se crea, quién lo crea, cómo y cuándo.

También permiten abstraer el proceso de ejemplarización porque permiten crear objetos sin repetir la misma implementación. Todo ello hace que el código resulte más flexible y sencillo.

Tipos de patrones de creación:

  • Abstract Factory - interfaz para crear familias de objetos sin mencionar sus clases.
  • Builder - interfaz que permite crear objetos complejos. Separa la construcción de un objeto de su representación para que un mismo proceso de construcción pueda dar lugar a representaciones distintas.
  • Método de fábrica - ofrece una interfaz para que las subclases creen objetos y permite a esas subclases decidir qué ejemplar de clase crear.
  • Prototipo - especifica los tipos de objetos que se crearán utilizando un ejemplar de prototipo y crea nuevos objetos copiando dicho prototipo.
  • Singleton - garantiza que haya un único ejemplar de alguna clase en una aplicación de un solo hilo, y ofrece un punto de acceso global a ese ejemplar.

A continuación, veremos cómo se pueden utilizar estos patrones en la programación MQL5. Para ello, responderemos a tres preguntas por cada patrón:

  • ¿Qué hace el patrón?
  • ¿Qué problema de diseño resuelve?
  • ¿Cómo usarlo en MQL5?


Fábrica abstracta

La fábrica abstracta es uno de los patrones de creación. Este patrón consta de Fábricas (Factory) y Productos (Product) y se utiliza para crear familias de objetos-productos relacionados sin crear directamente ejemplares de clase. Puede utilizarse cuando el número y los tipos generales de los objetos de producto son constantes, pero varían entre las distintas familias de productos.

¿Qué hace el patrón?

Esta patrón ofrece una interfaz para crear familias de objetos sin especificar sus clases, y estos objetos creados pueden estar relacionados o ser independientes. También se denomina Kit ("Conjunto de herramientas"). El siguiente esquema muestra cómo funciona y qué hace esta patrón.

Fábrica abstracta

Como podemos ver en el esquema, cuando tenemos muchos productos similares creados por diferentes fabricantes o fábricas, es difícil hacer cambios futuros a menos que utilicemos la patrón Fábrica Abstracta. Y con este patrón, el proceso resultará mucho más fácil.

En este patrón, definiremos una clase de fábrica abstracta que declarará una interfaz para las operaciones que crean productos abstractos. También tendremos una clase abstracta para cada fábrica con subclases para dos productos, y cada fábrica retornará diferentes productos dependiendo del producto que sea llamado por el cliente.

¿Qué problema de diseño resuelve?

Por lo tanto, podemos usar esta patrón cuando:

  • Necesitamos un sistema independiente.
  • Necesitamos un sistema configurado con una de las muchas familias de productos.
  • Necesitamos utilizar juntos una familia de productos relacionados según el diseño y hacer cumplir esta restricción.
  • Solo será necesario exponer las interfaces de la clase ofrecida, no su implementación.

Ejemplos de uso de una Fábrica Abstracta de este tipo:

  • Aislamiento de clases específicas, ya que la patrón permite controlar las clases de objetos creadas. Esto se consigue encapsulando la responsabilidad y el proceso de creación de los objetos, aislando al cliente de las clases de implementación; a través de interfaces abstractas de cliente, estas pueden manipular los ejemplares, y dentro de la fábrica específica de implementación los nombres de las clases de producto estarán aislados y no aparecerán en el código del cliente.
  • Esto facilitará la sustitución de familias de productos.

¿Cómo usarlo en MQL5?

Veamos cómo podemos implementar la estructura de la Fábrica abstracta como un archivo de inclusión. En este artículo analizaremos una estructura, pero podrá elegir el que mejor se adapte a sus necesidades.

Así pues, escribiremos el código de la estructura paso a paso:

Usando la palabra clave namespace, declararemos una función AbstractFactory para enumerar de forma interna todas las funciones que necesitamos dentro.

namespace AbstractFactory

Usando la palabra clave interface, declararemos la función AbstractProductA.

interface AbstractProductA
  {
  };

A continuación, de nuevo con la palabra clave interface, declararemos la función AbstractProductB con la variable void Interact en el cuerpo de la función.

interface AbstractProductB
  {
   void Interact(AbstractProductA*);
  };

Y declararemos una interfaz para las operaciones de creación de productos abstractos

interface AbstractFactory
  {
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };

Luego construiremos los productos A1 y A2; para ello, definiremos un objeto de producto que se creará utilizando una fábrica específica y sus herramientas, a través de una interfaz de producto abstracta.

class ProductA1:public AbstractProductA
  {
public:
                     ProductA1(void);
  };
void ProductA1::ProductA1(void) 
{
Print("Product A1 is constructed");
}
class ProductA2:public AbstractProductA
  {
public:
                     ProductA2(void);
  };
void ProductA2::ProductA2(void) 
{
Print("Product A2 is constructed");
}

Después construiremos los productos concretos B1 y B2, tras lo cual permitiremos la interacción con el producto abstracto A.

class ProductB1:public AbstractProductB
  {
public:
                     ProductB1(void);
   void              Interact(AbstractProductA*);
  };
void ProductB1::ProductB1(void) 
{
Print("Product B1 is constructed");
}
void ProductB1::Interact(AbstractProductA*src)
  {
   Print("Product B1: ",&this," is interacting with Product A: ",src);
  }
class ProductB2:public AbstractProductB
  {
public:
                     ProductB2(void);
   void              Interact(AbstractProductA*);
  };
void ProductB2::ProductB2(void) 
{
Print("Product B2 is constructed");
}
void ProductB2::Interact(AbstractProductA*src)
  {
   Print("Product B2: ",&this," is interacting with Product A: ",src);
  }

Acto seguido, declararemos las fábricas específicas Factory1 y Factory2; estas crearán y retornarán los productos A1, A2, B1, B2

class Factory1:public AbstractFactory
  {
public:
                     Factory1(void);
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };
void Factory1::Factory1(void)
  {
   Print("Factory 1: ",&this," is constructed");
  }
AbstractProductA* Factory1::CreateProductA(void)
  {
   Print("Factory 1 creates and returns Product A1");
   return new ProductA1;
  }
AbstractProductB* Factory1::CreateProductB(void)
  {
   Print("Factory 1 creates and returns Product B1");
   return new ProductB1;
  }
class Factory2:public AbstractFactory
  {
public:
                     Factory2(void);
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };
void Factory2::Factory2(void)
  {
   Print("Factory 2: ",&this," is constructed");
  }
AbstractProductA* Factory2::CreateProductA(void)
  {
   Print("Factory 2 creates and returns Product A2");
   return new ProductA2;
  }
AbstractProductB* Factory2::CreateProductB(void)
  {
   Print("Factory 2 creates and returns Product B2");
   return new ProductB2;
  }

Y declararemos la clase FactoryClient, utilizando las interfaces declaradas por la fábrica abstracta y el producto abstracto.

class FactoryClient
  {
public:
   void              Run(void);
   void              Switch(AbstractFactory*);
                     FactoryClient(AbstractFactory*);
                    ~FactoryClient(void);
protected:
   AbstractProductA* apa;
   AbstractProductB* apb;
   AbstractFactory*  factory;
   void              Delete(void);
  };
void FactoryClient::FactoryClient(AbstractFactory* af)
  {
   Print("Factory client created and received Abstract Factory ",af);
   Print("Factory client requests to accept/switch the factories");
   Switch(af);
  }
void FactoryClient::~FactoryClient(void)
  {
   Delete();
  }
void FactoryClient::Run(void)
  {
   Print("Factory client runs the abstract Product B");
   apb.Interact(apa);
  }
void FactoryClient::Delete(void)
  {
   delete apa;
   delete apb;
   delete factory;
  }
void FactoryClient::Switch(AbstractFactory *af)
  {
   string sFactory;
   StringConcatenate(sFactory,sFactory,factory);
   int iFactory=(int)StringToInteger(sFactory);
   if(iFactory>0)
     {
      Print("Factory client switches the old factory ",factory," to the new one ",af);
     }
   else
     {
      Print("Factory client accepts the new factory ",af);
     }
   Delete();
   factory=af;
   Print("Factory client saved the new factory");
   Print("Factory client requests its new factory to create the Product A");
   apa=factory.CreateProductA();
   Print("Factory client requests its new factory to create the Product B");
   apb=factory.CreateProductB();
  }

A continuación, definiremos la clase Client y ejecutaremos el patrón 

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Print("The client requests to create the Factory 1");
   Print("The client requests to create the Factory client");
   Print("The client requests the Factory client to manage the Factory 1");
   FactoryClient client(new Factory1);
   Print("The client requests the Factory client to operate");
   client.Run();
   Print("The client requests to create the new factory 2 and asks the factory client to switch factories");
   client.Switch(new Factory2);
   Print("The client requests the Factory client to run again");
   client.Run();
  }

Este es el bloque de código que tendremos:

namespace AbstractFactory
{
interface AbstractProductA
  {
  };
interface AbstractProductB
  {
   void Interact(AbstractProductA*);
  };
interface AbstractFactory
  {
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };
class ProductA1:public AbstractProductA
  {
public:
                     ProductA1(void);
  };
void ProductA1::ProductA1(void) 
{
Print("Product A1 is constructed");
}
class ProductA2:public AbstractProductA
  {
public:
                     ProductA2(void);
  };
void ProductA2::ProductA2(void) 
{
Print("Product A2 is constructed");
}
class ProductB1:public AbstractProductB
  {
public:
                     ProductB1(void);
   void              Interact(AbstractProductA*);
  };
void ProductB1::ProductB1(void) 
{
Print("Product B1 is constructed");
}
void ProductB1::Interact(AbstractProductA*src)
  {
   Print("Product B1: ",&this," is interacting with Product A: ",src);
  }
class ProductB2:public AbstractProductB
  {
public:
                     ProductB2(void);
   void              Interact(AbstractProductA*);
  };
void ProductB2::ProductB2(void) 
{
Print("Product B2 is constructed");
}
void ProductB2::Interact(AbstractProductA*src)
  {
   Print("Product B2: ",&this," is interacting with Product A: ",src);
  }
class Factory1:public AbstractFactory
  {
public:
                     Factory1(void);
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };
void Factory1::Factory1(void)
  {
   Print("Factory 1: ",&this," is constructed");
  }
AbstractProductA* Factory1::CreateProductA(void)
  {
   Print("Factory 1 creates and returns Product A1");
   return new ProductA1;
  }
AbstractProductB* Factory1::CreateProductB(void)
  {
   Print("Factory 1 creates and returns Product B1");
   return new ProductB1;
  }
class Factory2:public AbstractFactory
  {
public:
                     Factory2(void);
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };
void Factory2::Factory2(void)
  {
   Print("Factory 2: ",&this," is constructed");
  }
AbstractProductA* Factory2::CreateProductA(void)
  {
   Print("Factory 2 creates and returns Product A2");
   return new ProductA2;
  }
AbstractProductB* Factory2::CreateProductB(void)
  {
   Print("Factory 2 creates and returns Product B2");
   return new ProductB2;
  }
class FactoryClient
  {
public:
   void              Run(void);
   void              Switch(AbstractFactory*);
                     FactoryClient(AbstractFactory*);
                    ~FactoryClient(void);
protected:
   AbstractProductA* apa;
   AbstractProductB* apb;
   AbstractFactory*  factory;
   void              Delete(void);
  };
void FactoryClient::FactoryClient(AbstractFactory* af)
  {
   Print("Factory client created and received Abstract Factory ",af);
   Print("Factory client requests to accept/switch the factories");
   Switch(af);
  }
void FactoryClient::~FactoryClient(void)
  {
   Delete();
  }
void FactoryClient::Run(void)
  {
   Print("Factory client runs the abstract Product B");
   apb.Interact(apa);
  }
void FactoryClient::Delete(void)
  {
   delete apa;
   delete apb;
   delete factory;
  }
void FactoryClient::Switch(AbstractFactory *af)
  {
   string sFactory;
   StringConcatenate(sFactory,sFactory,factory);
   int iFactory=(int)StringToInteger(sFactory);
   if(iFactory>0)
     {
      Print("Factory client switches the old factory ",factory," to the new one ",af);
     }
   else
     {
      Print("Factory client accepts the new factory ",af);
     }
   Delete();
   factory=af;
   Print("Factory client saved the new factory");
   Print("Factory client requests its new factory to create the Product A");
   apa=factory.CreateProductA();
   Print("Factory client requests its new factory to create the Product B");
   apb=factory.CreateProductB();
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Print("The client requests to create the Factory 1");
   Print("The client requests to create the Factory client");
   Print("The client requests the Factory client to manage the Factory 1");
   FactoryClient client(new Factory1);
   Print("The client requests the Factory client to operate");
   client.Run();
   Print("The client requests to create the new factory 2 and asks the factory client to switch factories");
   client.Switch(new Factory2);
   Print("The client requests the Factory client to run again");
   client.Run();
  }
}


Constructor

El patrón emergente «Constructor» se utilizará cuando deseemos crear objetos complejos. Separará la construcción de un objeto de su representación. Un mismo proceso de diseño puede dar lugar a representaciones diferentes.

¿Qué hace el patrón?

El siguiente esquema muestra cómo funciona este patrón:

Constructor

Como podemos ver, según la estructura, tenemos un Builder que define una interfaz para crear las partes del objeto de producto, un Director que crea el objeto utilizando la interfaz Builder, y un ConcreteBuilder que se puede utilizar para hacer lo siguiente:

  • Diseñar y ensamblar piezas de un producto implementando la interfaz Builder.
  • Definir la representación y su seguimiento.
  • Obtener un producto proporcionando una interfaz.

El resultado final será un producto que representará un objeto complejo en construcción.

¿Qué problema de diseño resuelve?

Esta patrón se puede usar cuando necesitemos lo siguiente:

  • Un algoritmo independiente para crear un objeto complejo a partir de las partes que lo componen y ensamblar estas.
  • Registro de diferentes representaciones del lugar durante la construcción.

Qué permite hacer

  • Basándose en la representación interna del producto, el patrón Builder permite modificar estos.
  • La encapsulación del método de construcción y la representación de un objeto complejo ayudan a mejorar la modularidad, lo cual significa que el código se aislará para su construcción y representación.
  • Como resultado, tendremos un control más preciso del proceso de fabricación del producto.

¿Cómo usarlo en MQL5?

Veamos ahora la estructura de la implementación del patrón Constructor.

Usando una función de espacio de nombres, declararemos la función constructora Builder.

namespace Builder

En el cuerpo de la función crearemos la clase Product.

class Product
  {
public:
   void              Add(string);
   void              Show();
protected:
   string            parts[];
  };

Luego añadiremos una pieza

void Product::Add(string part)
  {
   int size=ArraySize(parts);
   ArrayResize(parts,size+1);
   parts[size]=part;
   Print("The product added ",part," to itself");
  }

Y mostraremos todos los detalles del producto

void Product::Add(string part)
  {
   int size=ArraySize(parts);
   ArrayResize(parts,size+1);
   parts[size]=part;
   Print("The product added ",part," to itself");
  }

Asimismo, crearemos una interfaz abstracta del constructor Builder para crear las partes A, B y C del producto.

interface Builder
  {
   void BuildPartA();
   void BuildPartB();
   void BuildPartC();
   Product* GetResult();
  };

Después crearemos una clase Director que construirá un objeto con una interfaz Builder utilizando una función de clase.

class Director
  {
public:
   void              Construct();
                     Director(Builder*);
                    ~Director();
protected:
   Builder*          builder;
  };

A continuación, crearemos un Builder y lo obtendremos en Director

void Director::Director(Builder *b)
  {
   builder=b;
   Print("The director created and received the builder ",b);
  }
void Director::~Director(void)
  {
   delete builder;
  }

El director empezará a crear las partes del producto A, B y C.

void Director::Construct(void)
  {
   Print("The director started the construction");
   Print("The director requestd its builder to build the product parts");
   builder.BuildPartA();
   builder.BuildPartB();
   builder.BuildPartC();
   Print("The director's builder constructed the product from parts");
  }

Crearemos una clase ConcreteBuilder con tres miembros públicos para las partes del producto y un miembro protegido para el producto.

class ConcreteBuilder:public Builder
  {
public:
   void              BuildPartA();
   void              BuildPartB();
   void              BuildPartC();
   Product*          GetResult();
protected:
   Product           product;
  };

El constructor añadirá las partes A, B y C al producto y, a continuación, retornará el producto utilizando las siguientes funciones.

void ConcreteBuilder::BuildPartA(void)
  {
   Print("The builder requests the product to add part A to itself");
   product.Add("part a");
   Print("The builder made the part of A and added it to the product");
  }
void ConcreteBuilder::BuildPartB(void)
  {
   Print("The builder requests the product to add part B to itself");
   product.Add("part b");
   Print("The builder made the part of B and added it to the product");
  }
void ConcreteBuilder::BuildPartC(void)
  {
   Print("The builder requests the product to add part C to itself");
   product.Add("part c");
   Print("The builder made part C and added it to the product");
  }
Product* ConcreteBuilder::GetResult(void)
  {
   Print("The builder is returns the product");
   return &product;
  }

Luego crearemos la clase de cliente Client con dos miembros públicos para los constructores Output y Run e iniciaremos el cliente. 

class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output() {return __FUNCTION__;}
void Client::Run()
  {
   Print("The client requests to create a new concrete builder");
   Builder* builder=new ConcreteBuilder;
   Print("The client requests to create a director and give him the builder");
   Director director(builder);
   Print("The client requests the director to perform the construction");
   director.Construct();
   Print("The client requests the builder to return the result product");
   Product* product=builder.GetResult();
   Print("The client is requests the product to describe itself");
   product.Show();
  }

A continuación le mostramos el código de la estructura Builder en su totalidad en un solo bloque.

namespace Builder
{
class Product
  {
public:
   void              Add(string);
   void              Show();
protected:
   string            parts[];
  };
void Product::Add(string part)
  {
   int size=ArraySize(parts);
   ArrayResize(parts,size+1);
   parts[size]=part;
   Print("The product added ",part," to itself");
  }
void Product::Show(void)
  {
   Print("The product shows all parts that it is made of");
   int total=ArraySize(parts);
   for(int i=0; i<total; i++)
      Print(parts[i]);
  }
interface Builder
  {
   void BuildPartA();
   void BuildPartB();
   void BuildPartC();
   Product* GetResult();
  };
class Director
  {
public:
   void              Construct();
                     Director(Builder*);
                    ~Director();
protected:
   Builder*          builder;
  };
void Director::Director(Builder *b)
  {
   builder=b;
   Print("The director created and received the builder ",b);
  }
void Director::~Director(void)
  {
   delete builder;
  }
void Director::Construct(void)
  {
   Print("The director started the construction");
   Print("The director requestd its builder to build the product parts");
   builder.BuildPartA();
   builder.BuildPartB();
   builder.BuildPartC();
   Print("The director's builder constructed the product from parts");
  }
class ConcreteBuilder:public Builder
  {
public:
   void              BuildPartA();
   void              BuildPartB();
   void              BuildPartC();
   Product*          GetResult();
protected:
   Product           product;
  };
void ConcreteBuilder::BuildPartA(void)
  {
   Print("The builder requests the product to add part A to itself");
   product.Add("part a");
   Print("The builder made the part of A and added it to the product");
  }
void ConcreteBuilder::BuildPartB(void)
  {
   Print("The builder requests the product to add part B to itself");
   product.Add("part b");
   Print("The builder made the part of B and added it to the product");
  }
void ConcreteBuilder::BuildPartC(void)
  {
   Print("The builder requests the product to add part C to itself");
   product.Add("part c");
   Print("The builder made part C and added it to the product");
  }
Product* ConcreteBuilder::GetResult(void)
  {
   Print("The builder is returns the product");
   return &product;
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output() {return __FUNCTION__;}
void Client::Run()
  {
   Print("The client requests to create a new concrete builder");
   Builder* builder=new ConcreteBuilder;
   Print("The client requests to create a director and give him the builder");
   Director director(builder);
   Print("The client requests the director to perform the construction");
   director.Construct();
   Print("The client requests the builder to return the result product");
   Product* product=builder.GetResult();
   Print("The client is requests the product to describe itself");
   product.Show();
  }
}


Método de fábrica

El patrón de creación "Método de fábrica" define una interfaz para crear un objeto y proporciona permiso a las subclases para seleccionar una clase con la que crear un ejemplar, y también permite a una clase diferir la creación de ejemplares para las subclases. Este patrón también se denomina Constructor Virtual (Virtual Constructor).

¿Qué hace el patrón?

Esquema de la estructura del método de fábrica:

Método de fábrica

Así, en este esquema tendremos los siguientes elementos:

  • El producto (Product) define la interfaz de los objetos creados por el método de fábrica.
  • El producto concreto (ConcreteProduct) se encarga de implementar la interfaz del producto.
  • El creador (creator) retorna el objeto de producto después de declararse el método de fábrica; puede ofrecer una implementación por defecto para el método de fábrica que retorne el objeto ConcreteProduct por defecto, y puede crear un objeto producto llamando al método de fábrica.
  • El creador concreto (ConcreteCreator) retorna un ejemplar de ConcreteProduct concreto redefiniendo el método de fábrica.

¿Qué problema de diseño resuelve?

Podemos usar el patrón de método de fábrica cuando:

  • Tenemos una clase que no puede predecir qué clase de objetos crear.
  • Una clase desea especificar los objetos creados por sus subclases.
  • Una de varias subclases auxiliares es delegada por las clases como clase responsable, y nosotros necesitamos saber qué subclase auxiliar es la delegada.

¿Cómo usarlo en MQL5?

A continuación, analizaremos cómo implementar la patrón del método de fábrica como un archivo include en MQL5.

Utilizando la palabra clave namespace, declararemos FactoryMethod

namespace FactoryMethod

Luego crearemos la interfaz del objeto Product

interface Product
  {
  };

Después crearemos la clase ConcreteProduct e implementaremos la interfaz del producto

class ConcreteProduct:public Product
  {
public:
                     ConcreteProduct(void);
  };
ConcreteProduct::ConcreteProduct(void)
  {
   "The concrete product: ",&this," created");
  }

A continuación, crearemos la clase Creator, devolveremos el objeto de tipo de producto, implementaremos el retorno del producto específico y crearemos el objeto de producto

class Creator
  {
public:
   virtual Product*  FactoryMethod(void)=0;
   void              AnOperation(void);
                    ~Creator(void);
protected:
   Product*          product;
  };
Creator::~Creator(void) {delete product;}
void Creator::AnOperation(void)
  {
   Print("The creator runs its operation");
   delete product;
   product=FactoryMethod();
   Print("The creator saved the product that received from the virtual factory method");
  }

Luego ejecutaremos el método de fábrica, y crearemos y retornaremos un nuevo producto concreto

class ConcreteCreator:public Creator
  {
public:
   Product*          FactoryMethod(void);
  };
Product* ConcreteCreator::FactoryMethod(void)
  {
   Print("The creator runs the factory method");
   Print("The concrete creator creates and returns the new concrete product");
   return new ConcreteProduct;
  }

Acto seguido, crearemos una clase Client con dos miembros públicos Output y Run.

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}

Asimismo, ejecutaremos la clase Client para solicitar el creador Creator, retornaremos el producto e iniciaremos el funcionamiento del creador.

void Client::Run(void)
  {
   Print("requests to make the creator");
   ConcreteCreator creator;
   Print("requests the creator to run its factory method to return the product");
   Product* product=creator.FactoryMethod();
   Print("requests the creator to run its operation");
   creator.AnOperation();
   delete product;
  }

A continuación le mostramos el código completo en un bloque que demuestra la estructura del método de fábrica.

namespace FactoryMethod
{
interface Product
  {
  };
class ConcreteProduct:public Product
  {
public:
                     ConcreteProduct(void);
  };
ConcreteProduct::ConcreteProduct(void)
  {
   Print("The concrete product: ",&this," created");
  }
class Creator
  {
public:
   virtual Product*  FactoryMethod(void)=0;
   void              AnOperation(void);
                    ~Creator(void);
protected:
   Product*          product;
  };
Creator::~Creator(void) {delete product;}
void Creator::AnOperation(void)
  {
   Print("The creator runs its operation");
   delete product;
   product=FactoryMethod();
   Print("The creator saved the product that received from the virtual factory method");
  }
class ConcreteCreator:public Creator
  {
public:
   Product*          FactoryMethod(void);
  };
Product* ConcreteCreator::FactoryMethod(void)
  {
   Print("The creator runs the factory method");
   Print("The concrete creator creates and returns the new concrete product");
   return new ConcreteProduct;
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Print("requests to make the creator");
   ConcreteCreator creator;
   Print("requests the creator to run its factory method to return the product");
   Product* product=creator.FactoryMethod();
   Print("requests the creator to run its operation");
   creator.AnOperation();
   delete product;
  }
}


Prototipo

Un prototipo es otro patrón de creación que especifica los tipos de objetos que se crearán usando un ejemplar de prototipo, y crea nuevos objetos copiando ese prototipo.

¿Qué hace el patrón?

A continuación le mostramos un esquema de la estructura del patrón de diseño «Prototipo»:

Prototipo

En este esquema tendremos los siguientes elementos:

  • El prototipo (prototype) crea una interfaz que puede ser clonada.
  • El prototipo concreto (ConcretePrototype) se clona a sí mismo implementando una operación para hacerlo.
  • El cliente (Client) pide al prototipo que se clone a sí mismo para crear un nuevo objeto.

¿Qué problema de diseño resuelve?

El patrón de prototipo puede utilizarse cuando:

  • Las clases cuyas ejemplares necesitamos crear se especificarán en tiempo de ejecución.
  • Debemos evitar construir una jerarquía de clases de fábrica que pueda ser paralela a la jerarquía de clases de producto.
  • Hay ejemplares de clases que pueden tener una de varias combinaciones diferentes de estados.

Qué lograremos al utilizar el patrón Prototipo:

  • Este permite añadir o eliminar productos fácilmente durante la ejecución, ya que el cliente tiene la capacidad de establecer y eliminar prototipos.
  • Permite especificar nuevos objetos indicando los valores de las variables del objeto.
  • Permite definir nuevos objetos modificando la estructura.
  • En lugar de crear un nuevo objeto, Prototype clonará el prototipo, lo cual significa reducir las subclases.
  • Permite a una aplicación configurar las clases dinámicamente.

¿Cómo usarlo en MQL5?

A continuación le mostramos el método para escribir el código de la estructura del patrón Prototype.

Usando la palabra clave namespace, declararemos la función Prototype.

namespace Prototype

Luego crearemos una clase o interfaz Prototype para que se clone a sí misma.

class Prototype
  {
public:
   virtual Prototype* Clone(void)=0;
                     Prototype(int);
protected:
   int               id;
  };
Prototype::Prototype(int i):id(i)
  {
   Print("The prototype ",&this,", id - ",id," is created");
  }

Y crearemos ConcretePrototype1 y ConcretePrototype2; aquí es donde se implementará la operación de autoclonación.

class ConcretePrototype1:public Prototype
  {
public:
                     ConcretePrototype1(int);
   Prototype*        Clone(void);
  };
ConcretePrototype1::ConcretePrototype1(int i):
   Prototype(i)
  {
   Print("The concrete prototype 1 - ",&this,", id - ",id," is created");
  }
Prototype* ConcretePrototype1::Clone(void)
  {
   Print("The cloning concrete prototype 1 - ",&this,", id - ",id);
   return new ConcretePrototype1(id);
  }
class ConcretePrototype2:public Prototype
  {
public:
                     ConcretePrototype2(int);
   Prototype*        Clone(void);
  };
ConcretePrototype2::ConcretePrototype2(int i):
   Prototype(i)
  {
   Print("The concrete prototype 2 - ",&this,", id - ",id," is created");
  }
Prototype* ConcretePrototype2::Clone(void)
  {
   Print("The cloning concrete prototype 2 - ",&this,", id - ",id);
   return new ConcretePrototype2(id);
  }

Después crearemos la clase Client para crear un nuevo objeto mediante la clonación de un prototipo de sí mismo.

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}

Inicio de un cliente que pide al prototipo que se clone a sí mismo.

void Client::Run(void)
  {
   Prototype* prototype;
   Prototype* clone;
   Print("requests to create the concrete prototype 1 with id 1");
   prototype=new ConcretePrototype1(1);
   Print("requests the prototype ",prototype," to create its clone");
   clone=prototype.Clone();
   delete prototype;
   delete clone;
   Print("requests to create the concrete prototype 2 with id 2");
   prototype=new ConcretePrototype2(2);
   Print("requests the prototype ",prototype," to create its clone");
   clone=prototype.Clone();
   delete prototype;
   delete clone;
  }

El código completo de la estructura del patrón «Prototipo» en un bloque al completo:

namespace Prototype
{
class Prototype
  {
public:
   virtual Prototype* Clone(void)=0;
                     Prototype(int);
protected:
   int               id;
  };
Prototype::Prototype(int i):id(i)
  {
   Print("The prototype ",&this,", id - ",id," is created");
  }
class ConcretePrototype1:public Prototype
  {
public:
                     ConcretePrototype1(int);
   Prototype*        Clone(void);
  };
ConcretePrototype1::ConcretePrototype1(int i):
   Prototype(i)
  {
   Print("The concrete prototype 1 - ",&this,", id - ",id," is created");
  }
Prototype* ConcretePrototype1::Clone(void)
  {
   Print("The cloning concrete prototype 1 - ",&this,", id - ",id);
   return new ConcretePrototype1(id);
  }
class ConcretePrototype2:public Prototype
  {
public:
                     ConcretePrototype2(int);
   Prototype*        Clone(void);
  };
ConcretePrototype2::ConcretePrototype2(int i):
   Prototype(i)
  {
   Print("The concrete prototype 2 - ",&this,", id - ",id," is created");
  }
Prototype* ConcretePrototype2::Clone(void)
  {
   Print("The cloning concrete prototype 2 - ",&this,", id - ",id);
   return new ConcretePrototype2(id);
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Prototype* prototype;
   Prototype* clone;
   Print("requests to create the concrete prototype 1 with id 1");
   prototype=new ConcretePrototype1(1);
   Print("requests the prototype ",prototype," to create its clone");
   clone=prototype.Clone();
   delete prototype;
   delete clone;
   Print("requests to create the concrete prototype 2 with id 2");
   prototype=new ConcretePrototype2(2);
   Print("requests the prototype ",prototype," to create its clone");
   clone=prototype.Clone();
   delete prototype;
   delete clone;
  }
}


El solitario

El propósito principal de este patrón es asegurarse de que solo hay un ejemplar de la clase y acceder a ella ofreciendo un punto global.

¿Qué hace el patrón?

La estructura del patrón «Solitario» (Singleton) se muestra en el siguiente esquema:

Solitario

En este patrón se define el funcionamiento de un ejemplar que concede permiso a los clientes para acceder a su ejemplar. El patrón Solitario también puede ser responsable de crear su propio ejemplar único.

¿Qué problema de diseño resuelve?

El patrón de diseño «Solitario» puede utilizarse cuando:

  • Resulta imperativo que solo haya un ejemplar de la clase y, sin embargo, debe ser accesible a los clientes a través de un punto de acceso conocido.
  • Necesitamos un ejemplar individual extendido a través de la creación de las subclases; podemos utilizarlo sin modificación.

A continuación le mostramos algunos ejemplos de resultados de la aplicación del patrón Solitario:

  • Al encapsular una clase en su único ejemplar en este patrón, controlaremos el acceso a esa ejemplar.
  • El uso de un patrón Solitario será más flexible que el uso de una operación de clase.

¿Cómo usarlo en MQL5?

Siguiendo el esquema que ya conocemos, ahora desarrollaremos el código del patrón Solitario en MQL5.

Usando la palabra clave namespace, declararemos la función Singleton.

namespace Singleton

Ahora crearemos la clase Singleton

class Singleton
  {
public:
   static Singleton* Instance(void);
   void              SingletonOperation(void);
   string            GetSingletonData(void);
protected:
                     Singleton(void);
   static Singleton* uniqueInstance;
   string            singletonData;
  };
Singleton* Singleton::uniqueInstance=NULL;

Luego crearemos un objeto Singleton

Singleton::Singleton(void)
  {
   Print("The singleton ",&this," is created");
  }

E iniciaremos una operación Singleton y estableceremos sus datos

void Singleton::SingletonOperation(void)
  {
   Print("runs the singleton operation > setting singleton data");
   singletonData="singleton data";
  }

Después se dará la lectura y recuperación de los datos de Singleton

string Singleton::GetSingletonData(void)
  {
   Print("reads and returns the singleton data");
   return singletonData;
  }

Se obtendrá o retornará un ejemplar único

Singleton* Singleton::Instance(void)
  {
   Print("The singleton instance method runs");
   if(!CheckPointer(uniqueInstance))
     {
      Print("The unique instance of the singleton is an empty");
      uniqueInstance=new Singleton;
      Print("singleton assigned to unique instance");
     }
   Print("The unique instance contains singleton: ",uniqueInstance);
   Print("returns the unique instance ",uniqueInstance," of the singleton");
   return uniqueInstance;
  }

A continuación, crearemos la clase de cliente Client

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}

El cliente Client accederá al Solitario Singleton a través de un ejemplar

void Client::Run(void)
  {
   Print("requests the singleton instance 1");
   Singleton* instance1=Singleton::Instance();
   Print("requests the singleton instance 2");
   Singleton* instance2=Singleton::Instance();
   string compareInstances=
      (instance1==instance2)?
      "instances 1 and instance 2 are the same objects":
      "instances are different objects";
   Print(compareInstances);
   Print("requests singleton operation on the instance 1");
   instance1.SingletonOperation();
   Print("requests singleton data by the singleton instance 2");
   string singletonData=instance2.GetSingletonData();
   Print(singletonData);
   delete instance1;
  }

A continuación le mostramos el código completo para el patrón Solitario en un solo bloque:

namespace Singleton
{
class Singleton
  {
public:
   static Singleton* Instance(void);
   void              SingletonOperation(void);
   string            GetSingletonData(void);
protected:
                     Singleton(void);
   static Singleton* uniqueInstance;
   string            singletonData;
  };
Singleton* Singleton::uniqueInstance=NULL;
Singleton::Singleton(void)
  {
   Print("The singleton ",&this," is created");
  }
void Singleton::SingletonOperation(void)
  {
   Print("runs the singleton operation > setting singleton data");
   singletonData="singleton data";
  }
string Singleton::GetSingletonData(void)
  {
   Print("reads and returns the singleton data");
   return singletonData;
  }
Singleton* Singleton::Instance(void)
  {
   Print("The singleton instance method runs");
   if(!CheckPointer(uniqueInstance))
     {
      Print("The unique instance of the singleton is an empty");
      uniqueInstance=new Singleton;
      Print("singleton assigned to unique instance");
     }
   Print("The unique instance contains singleton: ",uniqueInstance);
   Print("returns the unique instance ",uniqueInstance," of the singleton");
   return uniqueInstance;
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Print("requests the singleton instance 1");
   Singleton* instance1=Singleton::Instance();
   Print("requests the singleton instance 2");
   Singleton* instance2=Singleton::Instance();
   string compareInstances=
      (instance1==instance2)?
      "instances 1 and instance 2 are the same objects":
      "instances are different objects";
   Print(compareInstances);
   Print("requests singleton operation on the instance 1");
   instance1.SingletonOperation();
   Print("requests singleton data by the singleton instance 2");
   string singletonData=instance2.GetSingletonData();
   Print(singletonData);
   delete instance1;
  }
}


Conclusión

Como conclusión, podemos decir que este artículo proporciona información básica sobre el tema de los patrones de diseño. Ya hemos visto el tipo de «patrones de creación» que permite crear objetos que pueden reutilizarse, ampliarse y probarse. En otras palabras, hablamos de patrones que nos ayudan a escribir un código limpio.

Asimismo, hemos analizado los siguientes patrones de creación:

  • Fábrica abstracta
  • Constructor
  • Método de fábrica
  • Prototipo
  • El solitario

En este artículo, nos hemos familiarizado con la definición de patrones de diseño, y hemos aprendido sobre su utilidad cuando se utilizan en programación al escribir código orientado a objetos. Asimismo, hemos visto las estructuras de los patrones y los tipos de problemas que resuelven.

El tema de los patrones de diseño es bastante popular en el desarrollo de software. Una buena comprensión y uso de los conceptos le ayudará a escribir programas y a resolver los problemas que puedan surgir en su código. Le recomiendo leer y aprender más sobre el tema para poder resolver problemas frecuentes sin reinventar la rueda cada vez.

¿Qué más puede leer sobre este tema?

  • Design Patterns - Elements of Reusable Object-Oriented Software, de Eric Gamma, Richard Helm, Ralph Johnson, y John Vlissides
  • Design Patterns for Dummies, por Steve Holzner
  • Head First Design Patterns por Eric Freeman, Elisabeth Robson, Bert Bates y Kathy Sierra

Espero que este artículo te haya resultado útil y que haya aprendido algo nuevo sobre los patrones de diseño. También espero que este artículo le anime a aprender más sobre este tema. Además, permítame recordarle una vez más que este tema está implícitamente ligado a la programación orientada a objetos, de la que hablamos en el artículo "Programación Orientada a Objetos (POO) en MQL5". Si le ha gustado el artículo, si le ha resultado útil, lea mis artículos anteriores. En ellos describo la creación de sistemas de trading basados en varios indicadores técnicos populares, así como otras cosas interesantes en MQL5.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13622

Archivos adjuntos |
Builder.mqh (3 KB)
Factory_Method.mqh (1.51 KB)
Prototype.mqh (3.78 KB)
Singleton.mqh (2.01 KB)
Patrones de diseño en MQL5 (Parte 2): Patrones estructurales Patrones de diseño en MQL5 (Parte 2): Patrones estructurales
En este artículo, seguiremos estudiando los patrones de diseño que permiten a los desarrolladores crear aplicaciones extensibles y fiables no solo en MQL5, sino también en otros lenguajes de programación. Esta vez hablaremos de un tipo diferente: los patrones estructurales. Asimismo, aprenderemos a diseñar sistemas usando las clases disponibles para formar estructuras mayores.
Validación cruzada simétrica combinatoria en MQL5 Validación cruzada simétrica combinatoria en MQL5
El artículo muestra la implementación de la validación cruzada simétrica combinatoria en MQL5 puro para medir el grado de ajuste tras optimizar la estrategia usando el algoritmo completo lento del simulador de estrategias.
Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 4): Media móvil triangular - Señales del indicador Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 4): Media móvil triangular - Señales del indicador
Por asesor multidivisa en este artículo entendemos un asesor, o un robot comercial que puede operar (abrir/cerrar órdenes, gestionar órdenes como Trailing Stop Loss y Trailing Profit) con más de un par de símbolos desde un gráfico. Esta vez usaremos un solo indicador, a saber, la media móvil triangular en uno o varios marcos temporales.
Algoritmos de optimización de la población: Algoritmo de gotas de agua inteligentes (Intelligent Water Drops, IWD) Algoritmos de optimización de la población: Algoritmo de gotas de agua inteligentes (Intelligent Water Drops, IWD)
El artículo analiza un interesante algoritmo, las gotas de agua inteligentes, IWD, presente en la naturaleza inanimada, que simula el proceso de formación del cauce de un río. Las ideas de este algoritmo han permitido mejorar significativamente el anterior líder de la clasificación, el SDS, y el nuevo líder (SDSm modificado); como de costumbre, se puede encontrar en el archivo del artículo.