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

Patrones de diseño en MQL5 (Parte 4): Patrones conductuales 2

MetaTrader 5Trading | 28 junio 2024, 16:27
117 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Introducción

En este artículo, analizaremos los patrones de diseño conductuales, completando el tema de los patrones de diseño en software y su aplicación en MQL5. Ya hemos definido todos los patrones de generación en el artículo "Patrones de diseño en MQL5 (Parte I)": Patrones de creación", así como todos los patrones estructurales en el artículo "Patrones de diseño en MQL5 (Parte 2): Patrones estructurales". En el artículo anterior Patrones de diseño en MQL5 (Parte 3): Patrones conductuales 1, vimos algunos patrones conductuales: cadena de responsabilidad, comando, intérprete, iterador y mediador. También hemos definido qué son los patrones de diseño, que pueden utilizarse para definir métodos de vinculación y gestión de objetos.

En este artículo examinaremos los demás patrones conductuales:

  • Recordatorio (Memento) - restaura un objeto a su estado almacenado capturando y externalizando su estado interno sin romper la encapsulación.
  • Observador (Observer) - define una dependencia "uno-hacia-muchos" entre objetos, cuando un objeto cambia su estado, todas sus dependencias son notificadas y actualizadas automáticamente.
  • Estado (State) - cambia el comportamiento de un objeto cuando cambia su estado interno. Parece que el objeto está cambiando de clase.
  • Estrategia (Strategy) - identifica una familia de algoritmos y garantiza su encapsulación e intercambiabilidad. La estrategia permite que el algoritmo cambie independientemente de los clientes que lo usen.
  • Método de patrón (Template Method) - identifica los fundamentos de un algoritmo en una operación, dejando algunos pasos a las subclases, permitiendo a las subclases volver a identificarlos sin cambiar la estructura de los algoritmos.
  • Visitante (Visitor) - identificar una nueva operación sin que tenga ningún efecto sobre las clases de elementos sobre los que se realiza la operación.
  • Conclusión

Antes hemos definido que los modelos conductuales son modelos relacionados con la definición y asignación de responsabilidades entre objetos. También definen cómo pueden interactuar los objetos entre sí y caracterizan un flujo de control complejo y difícil de seguir en cuanto al tiempo de ejecución. Nos permiten centrarnos únicamente en la manera en que interactúan los objetos, desviando la atención del flujo de control.

Si ha leído mis artículos anteriores de la serie, estará familiarizado con el enfoque para representar cada patrón mediante los siguientes puntos:

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

El tema está fuertemente ligado a la programación orientada a objetos, de la que hablamos en el artículo "Programación orientada a objetos (POO)". Espero que mis artículos mejoren sus habilidades de programación identificando uno de los temas más importantes: los patrones de diseño para escribir código limpio, extensible, reutilizable y bien probado.

¡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.

Recordatorio (Memento)

El patrón puede utilizarse para externalizar el estado de un objeto y ofrecer una función de reversión. También se conoce como token.

¿Qué hace el patrón?

Podemos aplicar el patrón cuando necesitemos guardar una instantánea del estado de un objeto para reconstruirla más tarde, y cuando una interfaz directa para recuperar el estado puede revelar detalles de la ejecución y romper el encapsulamiento del objeto. De esta forma, el patrón capturará y exportará el estado del objeto para su posterior recuperación. A continuación figura un diagrama con la estructura de este patrón, que muestra cómo podría funcionar:

Recordatorio (Memento)

El diagrama muestra que en este patrón intervienen los siguientes elementos:

  • Recordatorio (Memento) - mantiene el estado según sea necesario a discreción del Iniciador (Originador). No concede acceso a objetos distintos del Iniciador. Puede guardar tanta información sobre el estado interno del Iniciador como sea necesario a discreción del Iniciador. El Recordatorio tiene dos interfaces: estrecha o ancha, según el punto de vista del Cuidador (Caretaker) o del Iniciador.
  • Iniciador (Originator) - crea un recordatorio que contiene la instantánea actual del estado interno y restaura el estado interno.
  • Cuidador (Caretaker) - guarda el recordatorio sin comprobar su contenido.

Al usar este tipo de patrón, debemos considerar los siguientes escollos:

  • Alto consumo de recursos con una copia grande.
  • Pérdida de datos históricos, ya que el número de ranuras de instantáneas es limitado.
  • No revela más información que un recordatorio.

¿Qué problema resuelve el patrón?

  • Almacena los límites de la encapsulación.
  • Define interfaces estrechas y anchas.
  • Puede usarse para simplificar el iniciador.

¿Cómo lo utilizamos en MQL5?

Vamos a intentar usar el Recordatorio en MQL5 de la siguiente manera:

Declaramos la clase Memento utilizando la palabra clave class.

class Memento
  {
protected:
   string            m_state;
public:
   string            GetState(void);
   void              SetState(string);
                     Memento(string);
  };
Memento::Memento(string state):
   m_state(state)
  {
  }
string Memento::GetState(void)
  {
   return m_state;
  }
void Memento::SetState(string state)
  {
   m_state=state;
  }

Declaramos la clase Originator

class Originator
  {
protected:
   string            m_state;
public:
   void              SetMemento(Memento& memento);
   Memento*          CreateMemento(void);
   string            State(void);
   void              State(string);
  };
void Originator::SetMemento(Memento& memento)
  {
   m_state=memento.GetState();
  }
Memento* Originator::CreateMemento(void)
  {
   return new Memento(m_state);
  }
void Originator::State(string state)
  {
   m_state=state;
  }
string Originator::State(void)
  {
   return m_state;
  }

Declaramos una clase Caretaker

class Caretaker
  {
public:
   Memento*          memento;
                    ~Caretaker(void);
  };
Caretaker::~Caretaker(void)
  {
   if(CheckPointer(memento)==1)
     {
      delete memento;
     }
  }

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

//+------------------------------------------------------------------+
//|                                                      Memento.mqh |
//+------------------------------------------------------------------+
class Memento
  {
protected:
   string            m_state;
public:
   string            GetState(void);
   void              SetState(string);
                     Memento(string);
  };
Memento::Memento(string state):
   m_state(state)
  {
  }
string Memento::GetState(void)
  {
   return m_state;
  }
void Memento::SetState(string state)
  {
   m_state=state;
  }
class Originator
  {
protected:
   string            m_state;
public:
   void              SetMemento(Memento& memento);
   Memento*          CreateMemento(void);
   string            State(void);
   void              State(string);
  };
void Originator::SetMemento(Memento& memento)
  {
   m_state=memento.GetState();
  }
Memento* Originator::CreateMemento(void)
  {
   return new Memento(m_state);
  }
void Originator::State(string state)
  {
   m_state=state;
  }
string Originator::State(void)
  {
   return m_state;
  }
class Caretaker
  {
public:
   Memento*          memento;
                    ~Caretaker(void);
  };
Caretaker::~Caretaker(void)
  {
   if(CheckPointer(memento)==1)
     {
      delete memento;
     }
  }

Observador (Observer)

El Observador define una dependencia "uno-hacia-muchos" entre objetos, cuando un objeto cambia su estado, todas sus dependencias son notificadas y actualizadas automáticamente. El patrón también se conoce como Dependents (Dependientes) y Publish-Subscribe (Publicar-Suscribir).

¿Qué hace el patrón?

Podemos usar un patrón cuando necesitamos encapsular aspectos de una abstracción en objetos separados para poder modificarlos y reutilizarlos de forma independiente, cuando es necesario modificar otros objetos mientras se modifica uno y no sabemos cuántos objetos debemos modificar, y cuando necesitamos que un objeto envíe una notificación a otros objetos sin que haya comunicación entre esos objetos.

Considerando todo esto, el patrón puede representarse gráficamente de la siguiente manera:

Observador

Elementos del patrón:

  • Sujeto (subject) - identifica a los observadores y el número de los mismos. Existe una interfaz para conectar y desconectar objetos observadores.
  • Observador (observador) - identifica la interfaz actualizada para los objetos que necesitan ser notificados de cambios en el tema.
  • Objeto concreto (ConcreteSubject) - almacena el estado en objetos de Observador concreto (ConcreteObserver). Cuando se produce un cambio de estado, envía una notificación a los observadores.
  • Observador concreto (ConcreteObserver) - contribuye a los tres objetivos siguientes:
    • Guarda una referencia al objeto Sujeto concreto.
    • Preserva el estado.
    • Ejecuta la interfaz de actualización del observador.

El uso de un patrón puede plantear los siguientes problemas:

  • El observador no puede determinar qué objeto ha actualizado su estado.
  • Grandes actualizaciones.
  • Complejidad de depuración.

¿Qué problema resuelve el patrón?

Dependiendo de cómo entendamos el funcionamiento del patrón, podría ayudarnos en lo siguiente:

  • Ofrecer un vínculo abstracto entre el sujeto y el observador.
  • Ofrecer apoyo a la interacción.

¿Cómo lo utilizamos en MQL5?

Usamos la palabra clave de interface para declarar el ámbito del Observador para definir las clases y funciones necesarias.

interface Observer
  {
   void Update(string state);
  };

Utilizamos la palabra clave class para declarar un Sujeto

class Subject
  {
public:
                     Subject(void);
                    ~Subject(void);
   void              Attach(Observer* observer);
   void              Detach(Observer& observer);
   void              Notify(void);
   void              State(string state);
   string            State(void) {return m_state;}

protected:
   string            m_state;
   Observer*         m_observers[];

   int               Find(Observer& observer);
  };
Subject::Subject(void):
   m_state(NULL)
  {
  }
Subject::~Subject(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(CheckPointer(item)==1)
        {
         delete item;
        }
     }
  }
void Subject::State(string state)
  {
   m_state=state;
  }
void Subject::Notify(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      m_observers[i].Update(m_state);
     }
  }
void Subject::Attach(Observer *observer)
  {
   int size=ArraySize(m_observers);
   ArrayResize(m_observers,size+1);
   m_observers[size]=observer;
  }
void Subject::Detach(Observer &observer)
  {
   int find=Find(observer);
   if(find==-1)
      return;
   Observer* item=m_observers[find];
   if(CheckPointer(item)==1)
      delete item;
   ArrayRemove(m_observers,find,1);
  }
int Subject::Find(Observer &observer)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(item==&observer)
         return i;
     }
   return -1;
  }

Usamos de la palabra clave class para declarar un Sujeto concreto

class ConcreteSubject:public Subject
  {
  public:
   void              State(string state);
   string            State(void) {return m_state;}
  };
void ConcreteSubject::State(string state)
  {
   m_state=state;
  } 

Usamos la palabra clave class para declarar un Observador concreto

class ConcreteObserver:public Observer
  {
public:
   void              Update(string state);
                     ConcreteObserver(ConcreteSubject& subject);
protected:
   string            m_observer_state;
   ConcreteSubject*  m_subject;
  };
ConcreteObserver::ConcreteObserver(ConcreteSubject& subject):
   m_subject(&subject)
  {
  }
void ConcreteObserver::Update(string state)
  {
   m_observer_state=state;
  }

A continuación le mostramos el código completo del uso del patrón de diseño conductual "Observador" en MQL5:

//+------------------------------------------------------------------+
//|                                                     Observer.mqh |
//+------------------------------------------------------------------+
interface Observer
  {
   void Update(string state);
  };
class Subject
  {
public:
                     Subject(void);
                    ~Subject(void);
   void              Attach(Observer* observer);
   void              Detach(Observer& observer);
   void              Notify(void);
   void              State(string state);
   string            State(void) {return m_state;}

protected:
   string            m_state;
   Observer*         m_observers[];

   int               Find(Observer& observer);
  };
Subject::Subject(void):
   m_state(NULL)
  {
  }
Subject::~Subject(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(CheckPointer(item)==1)
        {
         delete item;
        }
     }
  }
void Subject::State(string state)
  {
   m_state=state;
  }
void Subject::Notify(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      m_observers[i].Update(m_state);
     }
  }
void Subject::Attach(Observer *observer)
  {
   int size=ArraySize(m_observers);
   ArrayResize(m_observers,size+1);
   m_observers[size]=observer;
  }
void Subject::Detach(Observer &observer)
  {
   int find=Find(observer);
   if(find==-1)
      return;
   Observer* item=m_observers[find];
   if(CheckPointer(item)==1)
      delete item;
   ArrayRemove(m_observers,find,1);
  }
int Subject::Find(Observer &observer)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(item==&observer)
         return i;
     }
   return -1;
  }
class ConcreteSubject:public Subject
  {
  public:
   void              State(string state);
   string            State(void) {return m_state;}
  };
void ConcreteSubject::State(string state)
  {
   m_state=state;
  }  
class ConcreteObserver:public Observer
  {
public:
   void              Update(string state);
                     ConcreteObserver(ConcreteSubject& subject);
protected:
   string            m_observer_state;
   ConcreteSubject*  m_subject;
  };
ConcreteObserver::ConcreteObserver(ConcreteSubject& subject):
   m_subject(&subject)
  {
  }
void ConcreteObserver::Update(string state)
  {
   m_observer_state=state;
  }

Estado (State)

El patrón permite que un objeto cambie su comportamiento si cambia su estado interno. El objeto modificará su clase. También se conoce como Objects for States (Objetos para estados). Podemos usarlo cuando el comportamiento de un objeto depende de su estado, cambiando el comportamiento del objeto en función del estado durante la ejecución. También se puede usar cuando tenemos operaciones con grandes operadores condicionales, y además todo depende del estado del objeto.

¿Qué hace el patrón?

A continuación le mostramos cómo funciona el patrón:

Estado

Vamos a considerar los elementos presentes en el esquema anterior:

Contexto (context) - identifica la interfaz requerida por el cliente. También admite una subclase de ejemplar ConcreteState que identifica el estado actual.

Estado (state) - identifica una interfaz para encapsular el comportamiento de un estado de contexto concreto.

Subclases de estado concreto (ConcreteState subclasses) - la subclase ejecuta el comportamiento del estado de contexto. Esto se aplica a todas las subclases.

De acuerdo con lo que dijimos sobre el patrón "Estado", se puede utilizar cuando tenemos un objeto que se comporta de manera diferente dependiendo de su estado actual. Pero hay trampas al usar esta patrón: tendremos más clases, lo cual significa más código.

¿Qué problema resuelve el patrón?

No obstante, también hay ventajas:

  • el patrón ayuda a localizar el comportamiento específico de cada estado y separa el comportamiento según las características de cada estado.
  • Define explícitamente las transiciones entre diferentes estados.
  • Ayuda a compartir objetos de estado.

¿Cómo lo utilizamos en MQL5?

A continuación le presentamos los pasos del método para utilizar el patrón en MQL5:

Declaramos la clase Context

class Context;

Declaramos la interfaz del patrón

interface State
  {
   void Handle(Context& context);
  };

Declaramos el objeto m_state

State*            m_state;

Declaramos la clase Context

class Context
  {
public:
                     Context(State& state);
                    ~Context(void);
   State*            State(void) {return m_state;}
   void              State(State& state);
   void              Request(void);
  };
Context::~Context(void)
  {
   if(CheckPointer(m_state)==1)
      delete m_state;
  }
void Context::State(State& state)
  {
   delete m_state;
   m_state=&state;
  }
void Context::Request(void)
  {
   m_state.Handle(this);
  }

Declaramos la clase ConcreteStateA

class ConcreteStateA:public State
  {
public:
   void              Handle(Context& context);
  };
void ConcreteStateA::Handle(Context& context)
  {
   context.State(new ConcreteStateB);
  }

Declaramos la clase ConcreteStateB

class ConcreteStateB:public State
  {
public:
   void              Handle(Context& context);
  };
void ConcreteStateB::Handle(Context& context)
  {
   context.State(new ConcreteStateA);
  }

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

//+------------------------------------------------------------------+
//|                                                        State.mqh |
//+------------------------------------------------------------------+
class Context;
interface State
  {
   void Handle(Context& context);
  };
State*            m_state;
class Context
  {
public:
                     Context(State& state);
                    ~Context(void);
   State*            State(void) {return m_state;}
   void              State(State& state);
   void              Request(void);
  };
Context::~Context(void)
  {
   if(CheckPointer(m_state)==1)
      delete m_state;
  }
void Context::State(State& state)
  {
   delete m_state;
   m_state=&state;
  }
void Context::Request(void)
  {
   m_state.Handle(this);
  }
  
class ConcreteStateA:public State
  {
public:
   void              Handle(Context& context);
  };
void ConcreteStateA::Handle(Context& context)
  {
   context.State(new ConcreteStateB);
  }

class ConcreteStateB:public State
  {
public:
   void              Handle(Context& context);
  };
void ConcreteStateB::Handle(Context& context)
  {
   context.State(new ConcreteStateA);
  }

Estrategia (Strategy)

El patrón identifica un grupo de algoritmos, los encapsula y los hace intercambiables. Esto permite que el algoritmo cambie independientemente de los clientes que lo usen. Así, podremos utilizarlo cuando necesitemos permitir la selección de algoritmos durante la ejecución y cuando necesitemos excluir sentencias condicionales. El patrón también se conoce como Policy (política).

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

  • Muchas clases relacionadas solo difieran en su comportamiento. Las estrategias proporcionen un método para configurar el comportamiento de una clase de muchas maneras.
  • Tengamos que utilizar distintas variantes del mismo algoritmo. Y también necesitemos la capacidad de seleccionar el algoritmo durante la ejecución.
  • El algoritmo contiene los datos a los que los clientes no deberían tener acceso.
  • La clase define múltiples comportamientos que se manifiestan como múltiples operadores condicionales en sus operaciones. En lugar de un conjunto de ramas condicionales relacionadas, puede estar en una clase de estrategia independiente.
  • Debemos eliminar las declaraciones condicionales.

¿Qué hace el patrón?

A continuación le mostramos cómo funciona el patrón:

Estrategia

Elementos del patrón:

  • Estrategia (strategy) - declara una interfaz común a todos los algoritmos soportados, que será utilizada por el contexto para llamar al algoritmo establecido por una estrategia particular (ConcreteStrategy).
  • ConcreteStrategy - el contexto implementa el algoritmo utilizando la interfaz de patrones.
  • Contexto (contect) - creado por el objeto Estrategia concreta, mantiene una referencia al objeto de estrategia y puede identificar una interfaz para dar acceso a la estrategia a sus datos.

Desventajas ocultas:

  • El cliente debe conocer las estrategias.
  • Hay un mayor número de clases.

¿Qué problema resuelve el patrón?

Consecuencias (ventajas) de uso del patrón:

  • Existe la reutilización porque podemos identificar grupos de algoritmos relacionados que pueden reutilizarse en contexto.
  • Esto puede verse como otra forma de admitir diferentes comportamientos en lugar de crear subclases.
  • Permite eliminar los operadores condicionales como parte de la estrategia.
  • Permite elegir entre diferentes implementaciones del mismo comportamiento.

¿Cómo lo utilizamos en MQL5?

Vamos a declarar la interfaz Strategy para declarar clases y funciones de forma interna:

interface Strategy
  {
   void AlgorithmInterface(void);
  };

Declaramos la clase Context

class Context
  {
public:
                     Context(Strategy& strategy);
                    ~Context(void);

   void              ContextInterface(void);
protected:
   Strategy*         m_strategy;
  };
Context::Context(Strategy& strategy)
  {
   m_strategy=&strategy;
  }
Context::~Context(void)
  {
   if(CheckPointer(m_strategy)==1)
      delete m_strategy;
  }
void Context::ContextInterface(void)
  {
   m_strategy.AlgorithmInterface();
  }

Declaramos la clase ConcreteStrategyA

class ConcreteStrategyA : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyA::AlgorithmInterface(void)
  {
  }

Declaramos la clase ConcreteStrategyB

class ConcreteStrategyB : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyB::AlgorithmInterface(void)
  {
  }

Declaramos la clase ConcreteStrategyC

class ConcreteStrategyC : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyC::AlgorithmInterface(void)
  {
  }  

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

//+------------------------------------------------------------------+
//|                                                     Strategy.mqh |
//+------------------------------------------------------------------+
interface Strategy
  {
   void AlgorithmInterface(void);
  };
class Context
  {
public:
                     Context(Strategy& strategy);
                    ~Context(void);

   void              ContextInterface(void);
protected:
   Strategy*         m_strategy;
  };
Context::Context(Strategy& strategy)
  {
   m_strategy=&strategy;
  }
Context::~Context(void)
  {
   if(CheckPointer(m_strategy)==1)
      delete m_strategy;
  }
void Context::ContextInterface(void)
  {
   m_strategy.AlgorithmInterface();
  }
class ConcreteStrategyA : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyA::AlgorithmInterface(void)
  {
  }
class ConcreteStrategyB : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyB::AlgorithmInterface(void)
  {
  }
class ConcreteStrategyC : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyC::AlgorithmInterface(void)
  {
  }  

Método de patrón (Template Method)

Un patrón puede utilizarse para reconocer los componentes principales de un algoritmo dentro de una operación y delegar ciertos pasos a subclases. Esto permite a las subclases redefinir estos pasos sin cambiar la estructura general del algoritmo.

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

  • Tenemos un algoritmo y solo necesitamos cambiar algunos pasos en él sin afectar a la estructura general.
  • Solo tenemos que implementar los aspectos invariantes del algoritmo una vez y delegar la responsabilidad de implementar el comportamiento modificable a las subclases.
  • Necesitamos eliminar la duplicación de código cuando el comportamiento común a las subclases debe considerar y localizarse en una clase común.
  • Necesitamos controlar las extensiones de las subclases.

¿Qué hace el patrón?

A continuación le mostramos cómo funciona el patrón:

 Método de patrones

Elementos del patrón:

  • Clase abstracta (AbstractClass) - se utiliza para especificar operaciones primitivas abstractas que deben ser definidas por subclases específicas para implementar los distintos pasos del algoritmo. También puede usarse para ejecutar un método de patrón que describa la estructura del algoritmo. El método llama tanto a operaciones primitivas como a operaciones especificadas en la Clase Abstracta o pertenecientes a otros objetos.
  • Clase concreta (ConcreteClass) - se utiliza para realizar las operaciones primitivas necesarias para llevar a cabo los pasos de un algoritmo específico de una subclase.

¿Qué problema resuelve el patrón?

Ventajas e inconvenientes:

  • Usar un patrón nos permite crear código reutilizable porque es un método básico de reutilización de código, especialmente en las bibliotecas de clases.
  • Como hemos mencionado antes, esto puede permitirnos cambiar los pasos del algoritmo sin modificar su estructura.

Sin embargo, debemos tener en cuenta que todas las clases deben seguir el algoritmo sin excepciones.

¿Cómo lo utilizamos en MQL5?

Declaramos una clase abstracta utilizando la palabra clave class

class AbstractClass
  {
public:
   virtual void      PrimitiveOperation1(void)=0;
   virtual void      PrimitiveOperation2(void)=0;
   virtual void      TemplateMethod(void);
  };
void AbstractClass::TemplateMethod(void)
  {
   PrimitiveOperation1();
   PrimitiveOperation2();
  }

Declarar una clase definida usando la palabra clave class

  class ConcreteClass : public AbstractClass
  {
public:
   void              PrimitiveOperation1(void);
   void              PrimitiveOperation2(void);
  };
void ConcreteClass::PrimitiveOperation1(void)
  {
  }
void ConcreteClass::PrimitiveOperation2(void)
  {
  }

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

//+------------------------------------------------------------------+
//|                                              Template_Method.mqh |
//+------------------------------------------------------------------+
class AbstractClass
  {
public:
   virtual void      PrimitiveOperation1(void)=0;
   virtual void      PrimitiveOperation2(void)=0;
   virtual void      TemplateMethod(void);
  };
void AbstractClass::TemplateMethod(void)
  {
   PrimitiveOperation1();
   PrimitiveOperation2();
  }
  class ConcreteClass : public AbstractClass
  {
public:
   void              PrimitiveOperation1(void);
   void              PrimitiveOperation2(void);
  };
void ConcreteClass::PrimitiveOperation1(void)
  {
  }
void ConcreteClass::PrimitiveOperation2(void)
  {
  }

Visitante (Visitor)

El patrón permite identificar una nueva operación sin afectar a las clases de elementos sobre los que se realiza la operación. Podemos usar este patrón cuando tenemos una estructura de objetos que contiene muchas clases de objetos e interfaces diferentes y necesitamos ejecutar operaciones en estos objetos, o tenemos muchas operaciones diferentes y no relacionadas que necesitan ser realizadas en objetos dentro de la estructura de objetos y queremos evitar "contaminar" nuestras clases con estas operaciones. Además, las clases que definen la estructura de un objeto raramente cambian, pero necesitamos definir nuevas operaciones sobre la estructura.

¿Qué hace el patrón?

El siguiente diagrama muestra el funcionamiento del patrón:

Visitante

Elementos del patrón:

  • Visitante (visitor) - para cada clase ConcreteElement, se declara una operación Visit en la estructura del objeto. El nombre y la firma de la operación identifican unívocamente la clase que envía la solicitud del visitante. Este diseño ofrece acceso directo al elemento a través de su interfaz especial para que el visitante pueda identificar la clase específica del elemento que está visitando.
  • Visitante concreto (ConcreteVisitor) - implementa cada operación declarada por el visitante. Cada operación implementa un fragmento del algoritmo definido para la clase de objeto correspondiente en la estructura. El visitante concreto ofrece contexto para el algoritmo y almacena su estado local. Esta condición suele representar una acumulación de resultados durante la iteración de la estructura.
  • Elemento concreto (ConcreteElement) - define una operación de recepción que acepta un visitante como argumento.
  • Elemento concreto (ConcreteElement) - realiza una operación de recepción que toma un visitante como argumento.
  • Estructura de objeto (ObjectStructure) - puede enumerar sus elementos, ofreciendo una interfaz de alto nivel que permita al visitante visitar los elementos. La estructura puede ser compuesta o una colección.

¿Qué problema resuelve el patrón?

Según lo que hemos mencionado sobre el patrón, este tiene las siguientes ventajas:

  • Permite añadir fácilmente nuevas operaciones.
  • Permite distinguir entre operaciones vinculadas y no vinculadas, ya que combina operaciones vinculadas y operaciones no vinculadas separadas.

¿Cómo lo utilizamos en MQL5?

Declaramos la interfaz del patrón

interface Visitor;

Declaramos la clase Element

class Element
  {
protected:
   Visitor*          m_visitor;
public:
                    ~Element(void);
   virtual void      Accept(Visitor* visitor)=0;
protected:
   void              Switch(Visitor* visitor);
  };
Element::~Element(void)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
  }
void Element::Switch(Visitor *visitor)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
   m_visitor=visitor;
  }

Declaramos la clase ConcreteElementA

class ConcreteElementA : public Element
  {
public:
   void              Accept(Visitor*);
   void              OperationA(void);
  };
void ConcreteElementA::OperationA(void)
  {
  }
void ConcreteElementA::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementA(&this);
  }

Declaramos la clase ConcreteElementB

class ConcreteElementB : public Element
  {
public:
   void              Accept(Visitor* visitor);
   void              OperationB(void);
  };
void ConcreteElementB::OperationB(void)
  {
  }
void ConcreteElementB::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementB(&this);
  }

Utilizamos la palabra clave interface para definir internamente los patrones VisitElementA y VisitElementB

interface Visitor
{
   void VisitElementA(ConcreteElementA*);
   void VisitElementB(ConcreteElementB*);
};

Declaramos la clase ConcreteVisitor1

class ConcreteVisitor1 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA* visitor);
   void              VisitElementB(ConcreteElementB* visitor);
  };
void ConcreteVisitor1::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor1::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }

Declaramos la clase ConcreteVisitor2

class ConcreteVisitor2 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA*);
   void              VisitElementB(ConcreteElementB*);
  };
void ConcreteVisitor2::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor2::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }

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

//+------------------------------------------------------------------+
//|                                                      Visitor.mqh |
//+------------------------------------------------------------------+
interface Visitor;
class Element
  {
protected:
   Visitor*          m_visitor;
public:
                    ~Element(void);
   virtual void      Accept(Visitor* visitor)=0;
protected:
   void              Switch(Visitor* visitor);
  };
Element::~Element(void)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
  }
void Element::Switch(Visitor *visitor)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
   m_visitor=visitor;
  }
class ConcreteElementA : public Element
  {
public:
   void              Accept(Visitor*);
   void              OperationA(void);
  };
void ConcreteElementA::OperationA(void)
  {
  }
void ConcreteElementA::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementA(&this);
  }
class ConcreteElementB : public Element
  {
public:
   void              Accept(Visitor* visitor);
   void              OperationB(void);
  };
void ConcreteElementB::OperationB(void)
  {
  }
void ConcreteElementB::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementB(&this);
  }
interface Visitor
  {
   void VisitElementA(ConcreteElementA*);
   void VisitElementB(ConcreteElementB*);
  };
class ConcreteVisitor1 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA* visitor);
   void              VisitElementB(ConcreteElementB* visitor);
  };
void ConcreteVisitor1::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor1::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }
class ConcreteVisitor2 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA*);
   void              VisitElementB(ConcreteElementB*);
  };
void ConcreteVisitor2::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor2::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }

Conclusión

Tras leer todos los artículos de esta serie, será capaz de identificar todos los tipos de patrones de diseño (de creación, estructurales y conductuales). Hemos definido cada una de ellos, considerado cómo funcionan y cuándo podemos usarlos, qué problemas se resuelven usando tal o cual patrón, y cómo podemos usar estos patrones en MQL5 para escribir código limpio, soportable, reutilizable y bien probado.

Hemos analizado las siguientes patrones:

  • Padres
    • Fábrica abstracta
    • Constructor
    • Método de fábrica
    • Prototipo
    • Solitario
  • Estructurales
    • Adaptador
    • Puente
    • Compositor
    • Decorador
    • Fachada
    • Peso mosca
    • Proxy
  • Patrones conductuales
    • Cadena de responsabilidad
    • Equipo
    • Intérprete
    • Iterador
    • Mediador
    • Recordatorio
    • Observador
    • Condición
    • Método de patrones
    • Visitante

Podrá leer el resto de los artículos de esta serie clicando en los enlaces siguientes:

Quiero volver a insistir en la importancia de entender los patrones de diseño, ya que pueden resultar muy útiles en la creación de cualquier software. Así que le recomiendo leer más literatura sobre este tema. A continuación adjuntamos algunos enlaces útiles:

  • 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

Espero que esta serie de artículos le sea útil y le ayude a mejorar sus habilidades de programación en general y de programación MQL5 en particular. En la sección de publicaciones podrá leer mis otros artículos, incluyendo la programación MQL5 y la creación de sistemas comerciales basados en indicadores técnicos populares como medias móviles, RSI, Bandas de Bollinger y MACD. Espero que también le resulten útiles.

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

Archivos adjuntos |
Memento.mqh (1.34 KB)
Observer.mqh (2.5 KB)
State.mqh (1.19 KB)
Strategy.mqh (1.24 KB)
Visitor.mqh (2.14 KB)
Redes neuronales: así de sencillo (Parte 69): Restricción de la política de comportamiento basada en la densidad de datos offline (SPOT) Redes neuronales: así de sencillo (Parte 69): Restricción de la política de comportamiento basada en la densidad de datos offline (SPOT)
En el aprendizaje offline, utilizamos un conjunto de datos fijo, lo que limita la cobertura de la diversidad del entorno. Durante el proceso de aprendizaje, nuestro Agente puede generar acciones fuera de dicho conjunto. Si no hay retroalimentación del entorno, la corrección de las evaluaciones de tales acciones será cuestionable. Mantener la política del Agente dentro de la muestra de entrenamiento se convierte así en un aspecto importante para garantizar la solidez del entrenamiento. De eso hablaremos en este artículo.
Algoritmos de optimización de la población: Algoritmos de estrategias evolutivas (Evolution Strategies, (μ,λ)-ES y (μ+λ)-ES) Algoritmos de optimización de la población: Algoritmos de estrategias evolutivas (Evolution Strategies, (μ,λ)-ES y (μ+λ)-ES)
En este artículo, analizaremos un grupo de algoritmos de optimización conocidos como "estrategias evolutivas" (Evolution Strategies o ES). Se encuentran entre los primeros algoritmos basados en poblaciones que usan principios evolutivos para encontrar soluciones óptimas. Hoy le presentaremos los cambios introducidos en las variantes clásicas de ES y revisaremos la función de prueba y la metodología del banco de pruebas para los algoritmos.
Desarrollo de un sistema de repetición (Parte 42): Proyecto Chart Trade (I) Desarrollo de un sistema de repetición (Parte 42): Proyecto Chart Trade (I)
Vamos a crear algo más interesante. El código que mostré antes quedará completamente obsoleto. No quiero arruinar la sorpresa. Sigue el artículo para entender mejor. Desde el inicio de esta secuencia sobre cómo desarrollar un sistema de repetición/simulación, he dicho que la idea es usar la plataforma MetaTrader 5 de manera idéntica, tanto en el sistema que estamos desarrollando como en el mercado real. Es importante que esto se haga de manera adecuada. No querrás entrenar y aprender a luchar usando determinadas herramientas y en el momento de la pelea tener que usar otras.
Aprendizaje automático y Data Science (Parte 17): ¿Crece el dinero en los árboles? Bosques aleatorios en el mercado Fórex Aprendizaje automático y Data Science (Parte 17): ¿Crece el dinero en los árboles? Bosques aleatorios en el mercado Fórex
Este artículo le presentará los secretos de la alquimia algorítmica, introduciéndole con precisión las particularidades de los paisajes financieros. Asimismo, aprenderá cómo los bosques aleatorios transforman los datos en predicciones y le servirán de ayuda al navegar por las complejidades de los mercados financieros. Intentaremos identificar el papel de los bosques aleatorios en los datos financieros y comprobaremos si pueden ayudar a aumentar los beneficios.