English Русский Deutsch 日本語 Português
preview
Patrones de diseño en MQL5 (Parte 3): Patrones conductuales 1

Patrones de diseño en MQL5 (Parte 3): Patrones conductuales 1

MetaTrader 5Trading | 3 junio 2024, 14:48
170 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Introducción

Este artículo continúa la serie sobre patrones de diseño en ingeniería de software. En los dos artículos anteriores de esta serie definimos dos tipos de patrones: generativos y estructurales. En este artículo, definiremos los patrones conductuales. Asimismo, veremos cómo pueden ser útiles a la hora de crear, construir o desarrollar nuestro software y cómo podemos utilizarlos en MQL5 para crear nuestro software MetaTrader 5 para crear un software fiable, sostenible, reutilizable, bien probado y extensible.

Los principales conceptos que se usarán en el artículo son:

Le recomiendo leer también los artículos sobre patrones generativos y estructurales, para tener una visión general de uno de los temas más importantes en el desarrollo de software: los patrones de diseño, ya que pueden resultarle muy útiles en el desarrollo de aplicaciones.

¡Atención! Toda la información del presente artículo se ofrece «tal cual», únicamente con fines ilustrativos, y no supone ningún tipo de recomendación. El artículo no garantiza ningún resultado en absoluto. Todo lo que ponga en práctica usando este artículo como base, lo hará bajo su propia cuenta y riesgo; el autor no garantiza resultado alguno.

Patrones conductuales

En este artículo examinaremos el último tipo de patrones de diseño: los patrones conductuales. Ya sabemos que los patrones de generación ayudan a crear software o sistemas independientes mediante la creación, composición y representación de objetos, mientras que los patrones estructurales pueden usarse para crear estructuras más grandes utilizando los objetos y clases creados.

En este artículo, analizaremos los patrones conductuales relacionados con la asignación y atribución de responsabilidades a objetos. También definiremos cómo pueden interactuar los objetos entre sí; existen muchos patrones de este tipo, como los siguientes:

  • Cadena de responsabilidad (Chain of responsibility)
  • Comando (command)
  • Intérprete (Interpreter)
  • Iterador (Iterator)
  • Mediador (Mediator)
  • Recordatorio (Memento)
  • Observador (Observer)
  • Estado (State)
  • Estrategia (Strategy)
  • Método de patrones (Template Method)
  • Visitante (Visitor)

Como hay demasiados modelos para tratarlos en un solo artículo, nos centraremos en los cinco primeros:

  • Cadena de responsabilidad - ayuda a aplicar la separación entre emisor y receptor al permitir que más de un objeto gestione la solicitud del emisor. También ayuda a enlazar la obtención de objetos en una cadena y pasar una consulta a lo largo de la cadena para completar el procesamiento de objetos.
  • Comando - da permiso para establecer parámetros a clientes con diferentes solicitudes, poner en la cola o registrar solicitudes, y soporta operaciones de cancelación después de encapsular una solicitud como un objeto.
  • Intérprete - define la representación de la gramática de un lenguaje determinado, interpreta lo que se necesita en forma de frases en el lenguaje.
  • Iterador - si necesitamos un acceso consistente a los elementos de un objeto compuesto sin exponer su representación subyacente, este patrón ayudará a obtener el método requerido.
  • Mediador - define cómo un conjunto de objetos interactuará a través de un objeto encapsulado, y promueve la separación al permitirnos modificar independientemente la interacción de los objetos.

Si ha leído los dos artículos anteriores sobre patrones de diseño, ya estará familiarizado con el enfoque que utilizamos para describir cada patrón:

  • ¿Qué hace el patrón?
  • ¿Qué problema resuelve el patrón?
  • ¿Cómo lo utilizamos en MQL5?


Cadena de responsabilidad (Chain of responsibility)

En este apartado veremos la cadena de responsabilidad, averiguaremos lo que puede hacer y decidiremos cómo podemos utilizarla en MQL5. Cuando necesitamos procesar una solicitud de cliente, y si tenemos muchos objetos que pueden procesar solicitudes de clientes basados en la responsabilidad de cada uno, podemos usar esta patrón para gestionar este caso.

A pesar de las ventajas del uso de este patrón, tiene sus trampas:

  • El problema de la eficiencia en el caso de las cadenas largas.
  • No existe garantía de que la solicitud se procese porque la solicitud no tiene un receptor específico, por lo que esta solicitud se podrá transmitir a objetos de la cadena sin procesar. Además, la solicitud no podrá procesarse si no tenemos una cadena correctamente configurada.
¿Qué hace el patrón?

Un patrón puede ser útil para separar el emisor de cualquier solicitud y el receptor de la misma, proporcionando a múltiples objetos la capacidad de procesar la solicitud. Esto se logra fusionando los objetos receptores y transmitiendo la solicitud a todos ellos para comprobar cuál puede gestionar la solicitud.

A continuación le mostramos la estructura del patrón:

CADENA DE RESPONSABILIDAD

Vamos a considerar los elementos presentes en el esquema anterior:

  • Cliente - el cliente inicia la solicitud que será procesada por el objeto de la cadena.
  • Manejador - define la interfaz para procesar la solicitud. También puede implantar un enlace sucesor.
  • ConcreteHandler (manejador concreto) - objeto que procesa la solicitud dependiendo de su responsabilidad. Tiene acceso a un sucesor que puede transmitirlo cuando él no pueda procesar la solicitud.

¿Qué problema resuelve el patrón?

Esta patrón puede usarse si se cumple lo siguiente:

  • Tenemos muchos objetos que pueden gestionar solicitudes.
  • Debemos separar al emisor del receptor.
  • Necesitamos crear una solicitud a uno de un conjunto de objetos sin mencionar el receptor.
  • Tenemos un conjunto de objetos especificados dinámicamente que pueden gestionar la solicitud.

Así, esta patrón podrá hacer lo siguiente:

  • Reducir la conectividad, ya que ayuda a separar emisor y receptor, lo cual significará aplicar cambios independientes.
  • Esto ofrecerá flexibilidad a la hora de asignar y atribuir responsabilidades entre objetos.

¿Cómo lo utilizamos en MQL5?

En esta sección, aprenderemos cómo usar patrones en MQL5 para crear un software eficaz para MetaTrader 5. A continuación le mostraremos los pasos necesarios para codificar la cadena de responsabilidad en MQL5:

Primero declararemos el área Chain_Of_Responsibility para incluir funciones y variables de patrón usando la palabra clave namespace

namespace Chain_Of_Responsibility

Luego declararemos un miembro de la clase Handler que gestione las solicitudes del cliente y pueda implementar un enlace sucesor

class Handler
  {
public:
   Handler*          successor;
   virtual void      HandleRequest(int)=0;
                    ~Handler(void);
  };
Handler::~Handler(void)
  {
   delete successor;
  }

Y declararemos un miembro de la clase ConcreteHandler1 que gestionará las solicitudes de las que es responsable, o transmitirá la solicitud a su sucesor si puede procesarla

class ConcreteHandler1:public Handler
  {
public:
   void              HandleRequest(int);
  };
void ConcreteHandler1::HandleRequest(int request)
  {
   if(request==1)
      Print("The request: ",request,". The request handled by: ",&this);
   else
      if(CheckPointer(successor))
        {
         Print("The request: ",request,". The request cannot be handled by ",&this,", but it is forwarding to the successor...");
         successor.HandleRequest(request);
        }
  }

Después declararemos también la clase ConcreteHandler2 como miembro

class ConcreteHandler2:public Handler
  {
public:
   void              HandleRequest(int);
  };
void ConcreteHandler2::HandleRequest(int request)
  {
   if(request==2)
      Print("The request: ",request,". The request handled by: ",&this);
   else
      if(CheckPointer(successor))
        {
         Print("The request: ",request,". The request cannot be handled by ",&this,", forwarding to successor...");
         successor.HandleRequest(request);
        }
  }

Y declararemos una clase de cliente que iniciará una solicitud a un manejador específico en la cadena

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

La función de inicio del cliente consiste en enviar una solicitud a la cadena para que la procese o la pase a un sucesor

void   Client::Run()
  {
   Handler* h1=new ConcreteHandler1();
   Handler* h2=new ConcreteHandler2();
   h1.successor=h2;
   h1.HandleRequest(1);
   h1.HandleRequest(2);
   delete h1;
  }

A continuación le mostramos el código completo de la utilización del patrón cadena de responsabilidad en MQL5 en un bloque

//+------------------------------------------------------------------+
//|                                      Chain_Of_Responsibility.mqh |
//+------------------------------------------------------------------+
namespace Chain_Of_Responsibility
{
class Handler
  {
public:
   Handler*          successor;
   virtual void      HandleRequest(int)=0;
                    ~Handler(void);
  };
Handler::~Handler(void)
  {
   delete successor;
  }
class ConcreteHandler1:public Handler
  {
public:
   void              HandleRequest(int);
  };
void ConcreteHandler1::HandleRequest(int request)
  {
   if(request==1)
      Print("The request: ",request,". The request handled by: ",&this);
   else
      if(CheckPointer(successor))
        {
         Print("The request: ",request,". The request cannot be handled by ",&this,", but it is forwarding to the successor...");
         successor.HandleRequest(request);
        }
  }
class ConcreteHandler2:public Handler
  {
public:
   void              HandleRequest(int);
  };
void ConcreteHandler2::HandleRequest(int request)
  {
   if(request==2)
      Print("The request: ",request,". The request handled by: ",&this);
   else
      if(CheckPointer(successor))
        {
         Print("The request: ",request,". The request cannot be handled by ",&this,", but it is forwarding to successor...");
         successor.HandleRequest(request);
        }
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output()
  {
   return __FUNCTION__;
  }
void   Client::Run()
  {
   Handler* h1=new ConcreteHandler1();
   Handler* h2=new ConcreteHandler2();
   h1.successor=h2;
   h1.HandleRequest(1);
   h1.HandleRequest(2);
   delete h1;
  }
}


Comando (command)

El patrón Command también se conoce como Acción o Transacción. El patrón ayuda a encapsular la solicitud en un objeto, y esto nos permite establecer parámetros para diferentes solicitudes sin cambiar el emisor o el receptor, lo cual significa que ahora se aplicará la separación entre el emisor, el procesador y el receptor. Esto resulta muy útil cuando las clases tienen una amplia funcionalidad. El patrón también admite operaciones de cancelación.

Como casi todo, el patrón tiene sus desventajas:

  • Suele usarse junto con otros patrones.
  • Esto provoca la aparición de un gran número de clases y objetos para procesar diferentes comandos o solicitudes.
  • La creación de un objeto para cada comando contradice el diseño orientado a objetos.
¿Qué hace el patrón?

En pocas palabras, crea un iniciador encapsulado para obtener el comando y enviarlo al receptor.

Esquema de patrones:

COMANDO

Como podemos ver, el patrón incluye:

  • Command (Comando) - declara la interfaz para realizar la operación.
  • ConcreteCommand (Comando concreto) - crea un vínculo entre el receptor y una acción o comando. La ejecución se realiza llamando la operación correspondiente en el receptor.
  • Client (cliente) - crea un ConcreteCommand y establece su receptor.
  • Invoker (iniciador) - recibe la orden de ejecutar la solicitud.
  • Receiver (receptor) - identifica el método de las operaciones implicadas en el cumplimiento de la solicitud. Puede ser de cualquier clase.

¿Qué problema resuelve el patrón?

  • Podemos usar esta patrón en los siguientes casos:
  • Tenemos que parametrizar los objetos según la acción a realizar.
  • Necesitamos realizar operaciones como especificar, poner en cola y ejecutar en diferentes momentos.
  • Necesitamos algo que pueda usarse para apoyar una operación de cancelación.
  • En caso de fallo del sistema, necesitaremos soporte para registrar los cambios que luego puedan volver a aplicarse.
  • Necesitamos operaciones de alto nivel construidas sobre primitivas en forma de estructura de sistema.

¿Cómo lo utilizamos en MQL5?

En esta sección, consideraremos un método que se puede utilizar para usar el patrón de comandos en MQL5. Para ello, deberemos seguir los siguientes pasos:

Declaramos nuestro espacio de comandos para especificar funciones, variables, clases, etc., utilizando la palabra clave namespace

namespace Command

Declaramos la clase Receiver como un miembro que define un método para realizar operaciones de solicitud

class Receiver
  {
public:
                     Receiver(void);
                     Receiver(Receiver&);
   void              Action(void);
  };
Receiver::Receiver(void)
  {
  }
Receiver::Receiver(Receiver &src)
  {
  }
void Receiver::Action(void)
  {
  }

Declaramos la clase Command como miembro para declarar la interfaz de operación

class Command
  {
protected:
   Receiver*         m_receiver;
public:
                     Command(Receiver*);
                    ~Command(void);
   virtual void      Execute(void)=0;
  };
Command::Command(Receiver* receiver)
  {
   m_receiver=new Receiver(receiver);
  }
Command::~Command(void)
  {
   if(CheckPointer(m_receiver)==1)
     {
      delete m_receiver;
     }
  }

Declaramos la clase ConcreteCommand como miembro para crear una conexión entre el receptor y una acción o comando, e implementamos el método execute() después de llamar a la operación del receptor

class ConcreteCommand:public Command
  {
protected:
   int               m_state;
public:
                     ConcreteCommand(Receiver*);
   void              Execute(void);
  };
ConcreteCommand::ConcreteCommand(Receiver* receiver):
   Command(receiver),
   m_state(0)
  {
  }
void ConcreteCommand::Execute(void)
  {
   m_receiver.Action();
   m_state=1;
  }

Declaramos la clase Invoker como miembro para recibir la orden de ejecutar la solicitud

class Invoker
  {
public:
                    ~Invoker(void);
   void              StoreCommand(Command*);
   void              Execute(void);
protected:
   Command*          m_command;
  };
Invoker::~Invoker(void)
  {
   if(CheckPointer(m_command)==1)
     {
      delete m_command;
     }
  }
void Invoker::StoreCommand(Command* command)
  {
   m_command=command;
  }
void Invoker::Execute(void)
  {
   m_command.Execute();
  }

Vamos a declarar una clase de cliente como miembro para crear un comando específico, establecer su receptor y ejecutarlo

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Receiver receiver;
   Invoker invoker;
   invoker.StoreCommand(new ConcreteCommand(&receiver));
   invoker.Execute();
  }

A continuación le mostramos el código completo en un bloque

//+------------------------------------------------------------------+
//|                                                      Command.mqh |
//+------------------------------------------------------------------+
namespace Command
{
class Receiver
  {
public:
                     Receiver(void);
                     Receiver(Receiver&);
   void              Action(void);
  };
Receiver::Receiver(void)
  {
  }
Receiver::Receiver(Receiver &src)
  {
  }
void Receiver::Action(void)
  {
  }
class Command
  {
protected:
   Receiver*         m_receiver;
public:
                     Command(Receiver*);
                    ~Command(void);
   virtual void      Execute(void)=0;
  };
Command::Command(Receiver* receiver)
  {
   m_receiver=new Receiver(receiver);
  }
Command::~Command(void)
  {
   if(CheckPointer(m_receiver)==1)
     {
      delete m_receiver;
     }
  }
class ConcreteCommand:public Command
  {
protected:
   int               m_state;
public:
                     ConcreteCommand(Receiver*);
   void              Execute(void);
  };
ConcreteCommand::ConcreteCommand(Receiver* receiver):
   Command(receiver),
   m_state(0)
  {
  }
void ConcreteCommand::Execute(void)
  {
   m_receiver.Action();
   m_state=1;
  }
class Invoker
  {
public:
                    ~Invoker(void);
   void              StoreCommand(Command*);
   void              Execute(void);
protected:
   Command*          m_command;
  };
Invoker::~Invoker(void)
  {
   if(CheckPointer(m_command)==1)
     {
      delete m_command;
     }
  }
void Invoker::StoreCommand(Command* command)
  {
   m_command=command;
  }
void Invoker::Execute(void)
  {
   m_command.Execute();
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Receiver receiver;
   Invoker invoker;
   invoker.StoreCommand(new ConcreteCommand(&receiver));
   invoker.Execute();
  }
}


Intérprete (Interpreter)

Se trata de otro patrón conductual que puede utilizarse para ayudar a establecer la interacción entre objetos a través de un lenguaje determinado y definir la representación de reglas o gramáticas. Esta representación puede usarse para explicar e interpretar el contenido del lenguaje.

Posibles escollos de uso:

  • Complejidad de las reglas o de la gramática. Cuanto mayor sea la complejidad, más difícil será el soporte.
  • El patrón puede usarse en situaciones específicas.
¿Qué hace el patrón?

El patrón nos ayuda a describir cómo definir la gramática de un lenguaje, representar el contenido de ese lenguaje y obtener una interpretación de ese contenido.

Esquema de patrones:

Intérprete

Vamos a considerar los elementos presentes en el esquema anterior:

  • AbstractExpression - declara una operación de interpretación abstracta (contexto).
  • TerminalExpression - implementa la operación de interpretación vinculada con símbolos del terminal en la gramática, creando un ejemplar como requisito para cada símbolo de terminal en el contenido.
  • NonterminalExpression - para cada regla de la gramática se requiere una clase, esta soporta las variables de los ejemplares de AbstractExpression para cada regla. El elemento implementa la operación de interpretación de los símbolos no terminales en una gramática.
  • Contexto - contiene información global para el intérprete.
  • Cliente - construye un árbol sintáctico abstracto para representar el contenido en el lenguaje que necesitamos que la gramática defina, y llama a una operación de interpretación.

¿Qué problema resuelve el patrón?

Como ya hemos mencionado, podemos usar un patrón cuando tenemos un lenguaje que necesitamos interpretar; podemos definir o representar contenidos en ese lenguaje.

A continuación indicaremos los casos más apropiados para utilizar el patrón:

  • Tenemos una gramática de lenguaje simple, porque si la gramática es compleja, la jerarquía de clases se hará grande y esto puede llevar a un estado inmanejable.
  • El factor eficiencia en el intérprete no resulta tan importante.

Por lo tanto, al usar esta patrón, podemos obtener los siguientes beneficios:

  • El uso de este patrón facilita la actualización o ampliación de la gramática usando la herencia.
  • El patrón también simplifica la aplicación de la gramática.
  • El patrón facilita la adición de nuevas formas de interpretar las expresiones.

¿Cómo lo utilizamos en MQL5?

En esta sección, presentaremos un método sencillo para codificar o utilizar un patrón de este tipo. A continuación indicaremos los pasos para utilizar el intérprete en MQL5:

Primero declararemos el área del intérprete que utilizaremos para definir y declarar nuestras funciones, variables y clases usando la palabra clave namespace.

namespace Interpreter

Luego declararemos la clase context como miembro

class Context
  {
public:
   string            m_source;
   char              m_vocabulary;
   int               m_position;
   bool              m_result;
   //---
                     Context(char,string);
   void              Result(void);
  };
Context::Context(char vocabulary,string source):
   m_source(source),
   m_vocabulary(vocabulary),
   m_position(0),
   m_result(false)
  {
  }
void Context::Result(void)
  {
  }

Declararemos la clase Abstract como miembro

class AbstractExpression
  {
public:
   virtual void      Interpret(Context&)=0;
  };

Y declararemos la clase TerminalExpression como miembro para implementar el método de interpretación

class TerminalExpression:public AbstractExpression
  {
public:
   void              Interpret(Context&);
  };
void TerminalExpression::Interpret(Context& context)
  {
   context.m_result=
      StringSubstr(context.m_source,context.m_position,1)==
      CharToString(context.m_vocabulary);
  }

Después declararemos la clase NonterminalExpression como miembro

class NonterminalExpression:public AbstractExpression
  {
protected:
   AbstractExpression* m_nonterminal_expression;
   AbstractExpression* m_terminal_expression;
public:
   void              Interpret(Context&);
                    ~NonterminalExpression(void);
  };
NonterminalExpression::~NonterminalExpression(void)
  {
   delete m_nonterminal_expression;
   delete m_terminal_expression;
  }
void NonterminalExpression::Interpret(Context& context)
  {
   if(context.m_position<StringLen(context.m_source))
     {
      m_terminal_expression=new TerminalExpression;
      m_terminal_expression.Interpret(context);
      context.m_position++;
      if(context.m_result)
        {
         m_nonterminal_expression=new NonterminalExpression;
         m_nonterminal_expression.Interpret(context);
        }
     }
  }

A continuación, declararemos al cliente como miembro

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Context context_1('a',"aaa");
   Context context_2('a',"aba");
   AbstractExpression* expression;
   expression=new NonterminalExpression;
   expression.Interpret(context_1);
   context_1.Result();
   delete expression;
   expression=new NonterminalExpression;
   expression.Interpret(context_2);
   context_2.Result();
   delete expression;
  }

Más abajo mostramos el código completo para utilizar el intérprete como una sola unidad

//+------------------------------------------------------------------+
//|                                                  Interpreter.mqh |
//+------------------------------------------------------------------+
namespace Interpreter
{
class Context
  {
public:
   string            m_source;
   char              m_vocabulary;
   int               m_position;
   bool              m_result;
                     Context(char,string);
   void              Result(void);
  };
Context::Context(char vocabulary,string source):
   m_source(source),
   m_vocabulary(vocabulary),
   m_position(0),
   m_result(false)
  {
  }
void Context::Result(void)
  {
  }
class AbstractExpression
  {
public:
   virtual void      Interpret(Context&)=0;
  };
class TerminalExpression:public AbstractExpression
  {
public:
   void              Interpret(Context&);
  };
void TerminalExpression::Interpret(Context& context)
  {
   context.m_result=
      StringSubstr(context.m_source,context.m_position,1)==
      CharToString(context.m_vocabulary);
  }
class NonterminalExpression:public AbstractExpression
  {
protected:
   AbstractExpression* m_nonterminal_expression;
   AbstractExpression* m_terminal_expression;
public:
   void              Interpret(Context&);
                    ~NonterminalExpression(void);
  };
NonterminalExpression::~NonterminalExpression(void)
  {
   delete m_nonterminal_expression;
   delete m_terminal_expression;
  }
void NonterminalExpression::Interpret(Context& context)
  {
   if(context.m_position<StringLen(context.m_source))
     {
      m_terminal_expression=new TerminalExpression;
      m_terminal_expression.Interpret(context);
      context.m_position++;
      if(context.m_result)
        {
         m_nonterminal_expression=new NonterminalExpression;
         m_nonterminal_expression.Interpret(context);
        }
     }
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Context context_1('a',"aaa");
   Context context_2('a',"aba");
   AbstractExpression* expression;
   expression=new NonterminalExpression;
   expression.Interpret(context_1);
   context_1.Result();
   delete expression;
   expression=new NonterminalExpression;
   expression.Interpret(context_2);
   context_2.Result();
   delete expression;
  }
}


Iterador (Iterator)

Un iterador es uno de los patrones conductuales que definen el método de interacción o relación entre objetos. El patrón ayuda a posibilitar la presencia de un objeto añadido, como una lista, o nos ofrece un método para acceder a los elementos de una manera coherente sin revelar sus detalles básicos de presentación o su estructura interna. También se conoce como Cursor.

Desventajas ocultas:

  • Si tenemos una colección, no tendremos acceso a su índice.
  • El estado unidireccional en algunos casos en los que necesitamos, por ejemplo, ir al estado anterior (algunos lenguajes tienen soluciones para este problema).
  • En algunos casos, será más rápido crear un índice y recorrerlo que usar un patrón.
¿Qué hace el patrón?

El patrón puede admitir variantes en el caso de objetos añadidos complejos que pueden recorrerse de diferentes maneras, ya que el algoritmo de recorrido puede actualizarse fácilmente sustituyendo el ejemplar del iterador y definiendo subclases que admitan los recorridos actualizados. Esto simplificará la interfaz del objeto añadido. El seguimiento del estado del recorrido de un iterador nos permitirá procesar múltiples recorridos al mismo tiempo.

Esquema de patrones:

ITERADOR

El esquema contiene los siguientes elementos:

  • Iterator (iterador) - define una interfaz que se puede utilizar para acceder y desplazarse por los elementos.
  • ConcreteIterator (Iterador determinado) - permite implementar una interfaz de iterador y realizar un seguimiento del recorrido del añadido.
  • Aggregate (Complejo) - ayuda a identificar una interfaz que puede utilizarse para crear un iterador de objetos.
  • ConcreteAggregate (Complejo determinado) - implementa la interfaz de iteradores para obtener un ejemplar de un ConcreteIterator coincidente como retorno.

¿Qué problema resuelve el patrón?

El patrón de iterador puede utilizarse en los siguientes casos:

  • Si necesitamos acceder al contenido de un objeto añadido, pero no necesitamos exponer su representación interna.
  • Si tenemos muchos recorridos de objetos añadidos, tenemos que ofrecer soporte.
  • Si tenemos diferentes estructuras añadidas y necesitamos proporcionar una única interfaz para recorrerlas.

¿Cómo lo utilizamos en MQL5?

Veamos cómo usar esta patrón en MQL5 siguiendo los pasos que se indican a continuación:

Definición de ERRITERAOR-UT-OF-BOUNDS usando el preprocesador #define

#define ERR_ITERATOR_OUT_OF_BOUNDS 1

Utilizaremos la palabra clave pattern y declararemos T como CurrentItem en la interfaz Iterator determinada

template<typename T>
interface Iterator
  {
   void     First(void);
   void     Next(void);
   bool     IsDone(void);
   T        CurrentItem(void);
  };

Además, utilizaremos la palabra clave template y declararemos T como operador en la interfaz Aggregate definida

template<typename T>
interface Aggregate
  {
   Iterator<T>*   CreateIterator(void);
   int            Count(void);
   T              operator[](int at);
   void           operator+=(T item);
  };

Implementación de la interfaz iterador y seguimiento del recorrido acumulativo como la posición actual después de declarar la clase ConcreteIterator

template<typename T>
class ConcreteIterator:public Iterator<T>
  {
public:
   void              First(void);
   void              Next(void);
   bool              IsDone(void);
   T                 CurrentItem(void);
                     ConcreteIterator(Aggregate<T>&);
protected:
   Aggregate<T>*     m_aggregate;
   int               m_current;
  };
template<typename T> 
   ConcreteIterator::ConcreteIterator(Aggregate<T>& aggregate):
   m_aggregate(&aggregate),
   m_current(0)
  {
  }
template<typename T>
void ConcreteIterator::First(void)
  {
   m_current=0;
  }
template<typename T>
void ConcreteIterator::Next(void)
  {
   m_current++;
   if(!IsDone())
     {
     }
  }
template<typename T>
bool ConcreteIterator::IsDone(void)
  {
   return m_current>=m_aggregate.Count();
  }
template<typename T>
string ConcreteIterator::CurrentItem(void)
  {
   if(IsDone())
     {
      SetUserError(ERR_ITERATOR_OUT_OF_BOUNDS);
      return NULL;
     }
   return m_aggregate[m_current];
  }

Implementar una interfaz de creación de iteradores para obtener un ejemplar de iterador concreto adecuado como valor de retorno.

class ConcreteAggregate:public Aggregate<string>
  {
public:
   Iterator<string>* CreateIterator(void);
   int               Count(void);
   void              operator+=(string item);
   string            operator[](int at);
protected:
   string            m_items[];
  };
Iterator<string>* ConcreteAggregate::CreateIterator(void)
  {
   return new ConcreteIterator<string>(this);
  }
void ConcreteAggregate::operator+=(string item)
  {
   int size=ArraySize(m_items);
   ArrayResize(m_items,size+1);
   m_items[size]=item;
  }
string ConcreteAggregate::operator[](int at)
  {
   return m_items[at];
  }
int ConcreteAggregate::Count()
  {
   return ArraySize(m_items);
  }

A continuación le mostramos el código completo de el patrón en un bloque

//+------------------------------------------------------------------+
//|                                                201021_104101.mqh |
//+------------------------------------------------------------------+
#define ERR_ITERATOR_OUT_OF_BOUNDS 1
template<typename T>
interface Iterator
  {
   void     First(void);
   void     Next(void);
   bool     IsDone(void);
   T        CurrentItem(void);
  };
template<typename T>
interface Aggregate
  {
   Iterator<T>*   CreateIterator(void);
   int            Count(void);
   T              operator[](int at);
   void           operator+=(T item);
  };

template<typename T>
class ConcreteIterator:public Iterator<T>
  {
public:
   void              First(void);
   void              Next(void);
   bool              IsDone(void);
   T                 CurrentItem(void);
                     ConcreteIterator(Aggregate<T>&);
protected:
   Aggregate<T>*     m_aggregate;
   int               m_current;
  };
template<typename T> 
   ConcreteIterator::ConcreteIterator(Aggregate<T>& aggregate):
   m_aggregate(&aggregate),
   m_current(0)
  {
  }
template<typename T>
void ConcreteIterator::First(void)
  {
   m_current=0;
  }
template<typename T>
void ConcreteIterator::Next(void)
  {
   m_current++;
   if(!IsDone())
     {
     }
  }
template<typename T>
bool ConcreteIterator::IsDone(void)
  {
   return m_current>=m_aggregate.Count();
  }
template<typename T>
string ConcreteIterator::CurrentItem(void)
  {
   if(IsDone())
     {
      SetUserError(ERR_ITERATOR_OUT_OF_BOUNDS);
      return NULL;
     }
   return m_aggregate[m_current];
  }
class ConcreteAggregate:public Aggregate<string>
  {
public:
   Iterator<string>* CreateIterator(void);
   int               Count(void);
   void              operator+=(string item);
   string            operator[](int at);
protected:
   string            m_items[];
  };
Iterator<string>* ConcreteAggregate::CreateIterator(void)
  {
   return new ConcreteIterator<string>(this);
  }
void ConcreteAggregate::operator+=(string item)
  {
   int size=ArraySize(m_items);
   ArrayResize(m_items,size+1);
   m_items[size]=item;
  }
string ConcreteAggregate::operator[](int at)
  {
   return m_items[at];
  }
int ConcreteAggregate::Count()
  {
   return ArraySize(m_items);
  }


Mediador (Mediator)

Es otro patrón de diseño conductual que se puede utilizar para definir cómo los objetos pueden interactuar entre sí. Podemos usar el mediador cuando tenemos un conjunto de objetos y necesitamos definir un objeto encapsulado para describir cómo puede interactuar este conjunto de objetos. El mediador también aplica la separación, lo cual puede ser útil y nos permite modificar independientemente la interacción de los objetos.

Posibles escollos de uso:

  • Un mediador para todo.
  • Se utiliza con otros patrones.
¿Qué hace el patrón?

El patrón ayuda a establecer un método de interacción entre objetos sin mencionar cada objeto e forma explícita. Esto nos permite aplicar la separación entre objetos. También puede usarse como router y para la gestión de interacciones.

Esquema de patrones:

Mediador

Mediador (2)

Como podemos ver, el patrón incluye:

  • Mediator (Mediador) - define una interfaz para comunicarse con objetos pares.
  • ConcreteMediator (Mediador determinado) - implementa la colaboración mediante la coordinación de objetos pares. Sus pares son conocidos y pueden ser soportados con la ayuda de ConcreteMediator.
  • Colleague classes (Clases colegas) - el mediador de objetos es conocido por cada clase colega. Además, cada colega puede ponerse en contacto con su mediador siempre que lo necesite, o comunicarse con otro colega.

¿Qué problema resuelve el patrón?

El patrón puede utilizarse en los siguientes casos:

  • Tenemos un conjunto de objetos que pueden interactuar entre sí después de definir una forma de interactuar, pero la interacción en sí resulta más complicada.
  • Nos cuesta reutilizar un objeto cuando interactúa con otros.
  • Necesitamos implementar un comportamiento distribuido entre las clases sin crear muchas subclases.

Por consiguiente, podemos decir que el patrón:

  • Ayuda a limitar las subclases.
  • Ayudar a separar a los colegas.
  • Simplifica los protocolos de los objetos.
  • Hace posible que la interacción entre objetos sea abstracta.
  • Hace que la gestión esté centralizada.

¿Cómo lo utilizamos en MQL5?

Veamos cómo usar esta patrón en MQL5 siguiendo los pasos que se indican a continuación:

Primero utilizaremos la palabra clave interface para crear una interfaz Colleague

interface Colleague
  {
   void Send(string message);
  };
Luego usaremos la palabra clave interface para crear la interfaz del mediador
interface Mediator
  {
   void Send(string message,Colleague& colleague);
  };

Y declararemos la clase ConcreteColleague1

class ConcreteColleague1:public Colleague
  {
protected:
   Mediator*         m_mediator;
public:
                     ConcreteColleague1(Mediator& mediator);
   void              Notify(string message);
   void              Send(string message);
  };
ConcreteColleague1::ConcreteColleague1(Mediator& meditor):
   m_mediator(&meditor)
  {
  }
void ConcreteColleague1::Notify(string message)
  {
  }
void ConcreteColleague1::Send(string message)
  {
   m_mediator.Send(message,this);
  }

Después declararemos la clase ConcreteColleague2

class ConcreteColleague2:public Colleague
  {
protected:
   Mediator*         m_mediator;
public:
                     ConcreteColleague2(Mediator& mediator);
   void              Notify(string message);
   void              Send(string message);
  };
ConcreteColleague2::ConcreteColleague2(Mediator& mediator):
   m_mediator(&mediator)
  {
  }
void ConcreteColleague2::Notify(string message)
  {
  }
void ConcreteColleague2::Send(string message)
  {
   m_mediator.Send(message,this);
  }

Y declararemos la clase ConcreteMediator

class ConcreteMediator:public Mediator
  {
public:
   ConcreteColleague1*  colleague_1;
   ConcreteColleague2*  colleague_2;
   void              Send(string message,Colleague& colleague);
  };
void ConcreteMediator::Send(string message,Colleague& colleague)
  {
   if(colleague_1==&colleague)
      colleague_2.Notify(message);
   else
      colleague_1.Notify(message);
  }

A continuación le mostramos el código completo en un bloque

//+------------------------------------------------------------------+
//|                                                     Mediator.mqh |
//+------------------------------------------------------------------+
interface Colleague
  {
   void Send(string message);
  };
interface Mediator
  {
   void Send(string message,Colleague& colleague);
  };
class ConcreteColleague1:public Colleague
  {
protected:
   Mediator*         m_mediator;
public:
                     ConcreteColleague1(Mediator& mediator);
   void              Notify(string message);
   void              Send(string message);
  };
ConcreteColleague1::ConcreteColleague1(Mediator& meditor):
   m_mediator(&meditor)
  {
  }
void ConcreteColleague1::Notify(string message)
  {
  }
void ConcreteColleague1::Send(string message)
  {
   m_mediator.Send(message,this);
  }
class ConcreteColleague2:public Colleague
  {
protected:
   Mediator*         m_mediator;
public:
                     ConcreteColleague2(Mediator& mediator);
   void              Notify(string message);
   void              Send(string message);
  };
ConcreteColleague2::ConcreteColleague2(Mediator& mediator):
   m_mediator(&mediator)
  {
  }
void ConcreteColleague2::Notify(string message)
  {
  }
void ConcreteColleague2::Send(string message)
  {
   m_mediator.Send(message,this);
  }
class ConcreteMediator:public Mediator
  {
public:
   ConcreteColleague1*  colleague_1;
   ConcreteColleague2*  colleague_2;
   void              Send(string message,Colleague& colleague);
  };
void ConcreteMediator::Send(string message,Colleague& colleague)
  {
   if(colleague_1==&colleague)
      colleague_2.Notify(message);
   else
      colleague_1.Notify(message);
  }


Conclusión

Hoy hemos abarcado otro tipo de patrones de diseño que suponen uno de los temas más importantes en programación y desarrollo de software. Asimismo, hemos examinado algunos patrones conductuales e identificado qué son y cómo pueden resultar útiles para crear software reutilizable, extensible, mantenible y comprobable. También hemos explorado lo que cada patrón puede hacer, sus posibles inconvenientes y los problemas que se pueden resolver con él, así como a forma en la que podemos utilizar cada patrón en MQL5 para crear sistemas comerciales eficaces para MetaTrader 5.

En este artículo hemos analizado los siguientes patrones conductuales:

  • Cadena de responsabilidades
  • Comando (command)
  • Intérprete (Interpreter)
  • Iterador (Iterator)
  • Mediador (Mediator)

Si este es el primer artículo de la serie que ha leído, también le recomiendo leer mis artículos "Patrones de Diseño en MQL5 (Parte I): patrones de creación" y "Patrones de diseño en MQL5 (Parte 2)": patrones estructurales" para conocer otros tipos de patrones de diseño. Espero que le resulten útiles.

A continuación le presentamos algunas obras útiles para profundizar en el estudio de los patrones de diseño:

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

Si desea leer más artículos sobre la creación de sistemas comerciales para MetaTrader 5 utilizando los indicadores técnicos más populares, puede leer mis otros artículos. Encontrará los enlaces necesarios en la sección "Publicaciones" de mi perfil. Espero que le resulten útiles para su trading y que mejoren sus resultados tanto en la negociación como en el desarrollo.

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

Archivos adjuntos |
Command.mqh (4.04 KB)
Interpreter.mqh (4.83 KB)
Iterator.mqh (2.31 KB)
Mediator.mqh (1.72 KB)
Marcado de datos en el análisis de series temporales (Parte 4): Descomposición de la interpretabilidad usando el marcado de datos Marcado de datos en el análisis de series temporales (Parte 4): Descomposición de la interpretabilidad usando el marcado de datos
En esta serie de artículos, presentaremos varias técnicas de marcado de series temporales que pueden producir datos que se ajusten a la mayoría de los modelos de inteligencia artificial (IA). El marcado dirigido de datos puede hacer que un modelo de IA entrenado resulte más relevante para las metas y objetivos del usuario, mejorando la precisión del modelo y ayudando a este a dar un salto de calidad.
Escribimos el primer modelo de caja de cristal (Glass Box) en Python y MQL5 Escribimos el primer modelo de caja de cristal (Glass Box) en Python y MQL5
Los modelos de aprendizaje automático son difíciles de interpretar, y entender por qué los modelos no se ajustan a nuestras expectativas puede ayudarnos mucho a conseguir, en última instancia, el resultado deseado al utilizar técnicas tan avanzadas. Sin un conocimiento exhaustivo del funcionamiento interno del modelo, podría resultar difícil encontrar fallos que degraden el rendimiento. De este modo, podremos dedicar tiempo a crear funciones que no afecten a la calidad de la previsión. La conclusión es que, por muy bueno que sea un modelo, nos perderemos todas sus grandes ventajas por culpa de nuestros propios errores. Afortunadamente, existe una solución sofisticada y bien diseñada que permite ver con claridad lo que sucede bajo el capó del modelo.
Algoritmos de optimización de la población: Algoritmo de recocido simulado (Simulated Annealing, SA). Parte I Algoritmos de optimización de la población: Algoritmo de recocido simulado (Simulated Annealing, SA). Parte I
El algoritmo de recocido simulado es una metaheurística inspirada en el proceso de recocido de los metales. En nuestro artículo, realizaremos un análisis exhaustivo del algoritmo y mostraremos cómo muchas percepciones comunes y mitos que rodean a este método de optimización (el más popular y conocido) pueden ser incorrectos e incompletos. Anuncio de la segunda parte del artículo: "¡Conozca el algoritmo de recocido Isotrópico Simulado (Simulated Isotropic Annealing, SIA) del propio autor!"
Interpretación de modelos: Una comprensión más profunda de los modelos de aprendizaje automático Interpretación de modelos: Una comprensión más profunda de los modelos de aprendizaje automático
El aprendizaje automático es un campo desafiante y gratificante para cualquiera, independientemente de la experiencia que tenga. En este artículo, nos sumergiremos en el funcionamiento interno de los modelos creados, exploraremos el complejo mundo de las funciones, las predicciones y las soluciones eficientes, y comprenderemos claramente la interpretación de los modelos. Asimismo, aprenderemos el arte de hacer concesiones, mejorar las predicciones, clasificar la importancia de los parámetros y tomar decisiones sólidas. Este artículo le ayudará a mejorar el rendimiento de los modelos de aprendizaje automático y a sacar más partido de sus metodologías.