English Русский 中文 Español Deutsch 日本語
preview
Padrões de projeto no MQL5 (Parte 4): Padrões comportamentais 2

Padrões de projeto no MQL5 (Parte 4): Padrões comportamentais 2

MetaTrader 5Negociação | 31 maio 2024, 16:43
231 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Introdução

Neste artigo, examinaremos os padrões comportamentais, concluindo o tema dos padrões de projeto em software e sua aplicação no MQL5. Definimos todos os padrões criacionais no artigo "Padrões de projeto no MQL5 (Parte I): Padrões criacionais (Creational Patterns)", bem como todos os padrões estruturais no artigo "Padrões de projeto no MQL5 (Parte 2): Padrões estruturais". No artigo anterior Padrões de projeto no MQL5 (Parte 3): Padrões comportamentais 1, examinamos alguns padrões comportamentais: cadeia de responsabilidade, comando, intérprete, iterador e mediador. Também definimos o que são padrões de projeto, que podem ser usados para definir métodos de comunicação entre objetos e gerenciá-los.

Neste artigo, examinaremos os padrões comportamentais restantes:

  • Lembrete (Memento) - restaura um objeto ao estado salvo capturando e externalizando seu estado interno sem violar a encapsulação.
  • Observador (Observer) - define uma dependência "um para muitos" entre objetos - quando um objeto muda seu estado, todas as suas dependências são notificadas e atualizadas automaticamente.
  • Estado (State) - altera o comportamento de um objeto quando seu estado interno muda. Parece que o objeto muda sua classe.
  • Estratégia (Strategy) - identifica uma família de algoritmos, encapsula-os e os torna intercambiáveis. A estratégia permite que o algoritmo mude independentemente dos clientes que o utilizam.
  • Método de template (Template Method) - define os passos básicos de um algoritmo em uma operação, deixando alguns passos para as subclasses, permitindo que as subclasses redefinam esses passos sem alterar a estrutura do algoritmo.
  • Visitante (Visitor) - define uma nova operação sem causar impacto nas classes dos elementos sobre os quais a operação é realizada.
  • Considerações finais

Anteriormente, definimos que os padrões comportamentais são padrões relacionados à definição e distribuição de responsabilidades entre objetos. Eles também definem como os objetos podem interagir entre si e caracterizam o fluxo complexo de controle, que é difícil de acompanhar durante a execução. Eles permitem focar apenas na forma como os objetos interagem, desviando a atenção do fluxo de controle.

Se você leu meus artigos anteriores da série, está familiarizado com essa maneira de apresentar cada padrão:

  • O que o padrão faz?
  • Que problema o padrão resolve?
  • Como utilizá-lo no MQL5?

O tópico está intrinsecamente ligado à programação orientada a objetos, que discutimos no artigo "Programação orientada a objetos (OOP)". Espero que meus artigos permitam que você aperfeiçoe suas habilidades de programação, definindo um dos tópicos mais importantes: padrões de projeto para escrever um código limpo, expansível, reutilizável e bem testado.

Atenção! Todo o conteúdo deste artigo é fornecido "como está", destinado apenas para fins educacionais e não constitui uma recomendação de negociação. O artigo não garante nenhum resultado. Tudo o que você aplica na prática com base neste artigo é feito por sua conta e risco, e o autor não garante nenhum resultado.

Lembrete (Memento)

O padrão pode ser usado para externalizar o estado de um objeto, a fim de fornecer a função de rollback (desfazer). Ele também é conhecido como token.

O que o padrão faz?

Podemos aplicar o padrão quando precisamos salvar um instantâneo do estado de um objeto que será restaurado posteriormente, e quando a interface direta para obter o estado pode revelar detalhes de execução e violar a encapsulação do objeto. Assim, o padrão capturará e exportará o estado do objeto para posterior restauração. Abaixo está o diagrama da estrutura deste padrão, mostrando como ele pode funcionar:

Lembrete (Memento)

O diagrama mostra que os seguintes elementos participam deste padrão:

  • Lembrete (Memento) - salva o estado conforme necessário, a critério do Originador (Originator). Não fornece acesso ao objeto, exceto ao Originador. Pode armazenar tantas informações sobre o estado interno do Originador quanto necessário, a critério do Originador. O Lembrete possui duas interfaces: estreita ou ampla, dependendo do ponto de vista do Zelador (Caretaker) ou do Originador.
  • Originador (Originator) - cria o lembrete contendo o instantâneo atual do estado interno e restaura o estado interno.
  • Zelador (Caretaker) - salva o lembrete sem verificar seu conteúdo.

Ao usar este tipo de padrão, deve-se considerar as seguintes armadilhas:

  • Alto consumo de recursos ao fazer grandes cópias.
  • Perda de dados históricos, pois o número de slots para instantâneos é limitado.
  • Não revela nenhuma informação além do lembrete.

Que problema o padrão resolve?

  • Ele preserva os limites da encapsulação.
  • Define interfaces estreitas e amplas.
  • Pode ser usado para simplificar o originador.

Como utilizá-lo no MQL5?

Vamos tentar usar o Lembrete no MQL5 da seguinte maneira:

Declarar a classe Memento, usando a palavra-chave 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;
  }

Declarar a classe 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;
  }

Declarar a classe Caretaker.

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

Abaixo está o código completo do padrão em um bloco:

//+------------------------------------------------------------------+
//|                                                      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)

O Observador define uma dependência "um para muitos" entre objetos - quando um objeto muda seu estado, todas as suas dependências são notificadas e atualizadas automaticamente. O padrão também é conhecido como Dependents (dependentes) e Publish-Subscribe (publicação-assinatura).

O que o padrão faz?

Podemos usar o padrão quando precisamos encapsular aspectos da abstração em objetos separados, para poder alterá-los e reutilizá-los de forma independente, quando há necessidade de modificar outros objetos ao mudar um e não sabemos quantos objetos precisam ser modificados, assim como quando precisamos que um objeto envie notificações para outros objetos sem ligação entre esses objetos.

Levando em consideração tudo o que foi dito, o padrão pode ser representado graficamente da seguinte forma:

Observador (Observer)

Elementos do padrão:

  • Sujeito (Subject) - identifica os observadores e sua quantidade. Possui uma interface para anexar e desanexar objetos observadores.
  • Observador (Observer) - define a interface de atualização para os objetos que precisam ser notificados sobre as mudanças no sujeito.
  • Sujeito Concreto (ConcreteSubject) - armazena o estado nos objetos Observadores Concretos (ConcreteObserver). Quando ocorre uma mudança de estado, ele notifica os observadores sobre isso.
  • Observador Concreto (ConcreteObserver) - ajuda a alcançar os seguintes três objetivos:
    • Armazenar uma referência ao objeto Sujeito Concreto.
    • Armazenar o estado.
    • Implementar a interface de atualização do observador.

Ao usar o padrão, podem ocorrer as seguintes armadilhas:

  • O sujeito não pode determinar qual objeto atualizou seu estado.
  • Grandes atualizações.
  • Complexidade na depuração.

Que problema o padrão resolve?

Dependendo da nossa compreensão dos princípios de funcionamento do padrão, ele pode nos ajudar no seguinte:

  • Prover uma ligação abstrata entre o sujeito e o observador.
  • Garantir suporte à interação.

Como utilizá-lo no MQL5?

Utilize a palavra-chave interface para declarar a área do Observador para definir as classes e funções necessárias.

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

Utilize a palavra-chave class para declarar o Sujeito.

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;
  }

Utilize a palavra-chave class para declarar o Sujeito Concreto.

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

Utilize a palavra-chave class para declarar o 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;
  }

Abaixo está o código completo do uso do padrão de projeto comportamental "Observador" no 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)

O padrão permite que um objeto altere seu comportamento quando seu estado interno muda. O objeto parecerá mudar sua classe. Ele também é conhecido como Objects for States (objetos para estados). Podemos usá-lo quando o comportamento de um objeto depende de seu estado, alterando o comportamento do objeto dependendo do estado durante a execução. Também pode ser usado quando temos operações com grandes operadores condicionais, onde tudo depende do estado do objeto.

O que o padrão faz?

Abaixo está a operação do padrão:

Estado (State)

Vamos considerar os elementos presentes no diagrama acima:

Contexto (context) - identifica a interface necessária pelo cliente. Ele também suporta uma instância do subtipo do estado concreto (ConcreteState), que identifica o estado atual.

Estado (State) - define a interface para encapsular o comportamento do estado específico do contexto.

Subclasses de Estado Concreto (ConcreteState subclasses) - as subclasses implementam o comportamento do estado do contexto. Isso se aplica a cada subclasse.

De acordo com o que discutimos sobre o padrão "Estado", ele pode ser usado quando temos um objeto que se comporta de maneira diferente dependendo do seu estado atual. Mas ao usar esse padrão, há algumas armadilhas: teremos mais classes, o que significa mais código.

Que problema o padrão resolve?

No entanto, há também vantagens:

  • O padrão ajuda a localizar o comportamento específico de cada estado e a separar o comportamento com base nas características de cada estado.
  • Define explicitamente as transições entre diferentes estados.
  • Ajuda a reutilizar objetos de estado.

Como utilizá-lo no MQL5?

Abaixo estão os passos do método para usar o padrão no MQL5:

Declarar a classe Context.

class Context;

Declarar a interface do padrão.

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

Declarar o objeto m_state.

State*            m_state;

Declarar a classe 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);
  }

Declarar a classe ConcreteStateA.

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

Declarar a classe ConcreteStateB

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

Abaixo está o código completo do padrão em um bloco:

//+------------------------------------------------------------------+
//|                                                        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);
  }

Estratégia (Strategy)

O padrão identifica um grupo de algoritmos, encapsula-os e os torna intercambiáveis. Isso permite que o algoritmo mude independentemente dos clientes que o utilizam. Assim, podemos usá-lo quando precisamos permitir a escolha do algoritmo em tempo de execução e quando precisamos eliminar operadores condicionais. O padrão também é conhecido como Policy (política).

Portanto, podemos usar o padrão quando:

  • Muitas classes relacionadas diferem apenas no seu comportamento. Estratégias fornecem um método para configurar o comportamento da classe de uma das muitas maneiras.
  • Precisamos usar diferentes variações do mesmo algoritmo. E também precisamos da capacidade de escolher o algoritmo em tempo de execução.
  • O algoritmo contém dados aos quais os clientes não devem ter acesso.
  • A classe define muitas variações de comportamento que aparecem em suas operações como vários operadores condicionais. Em vez de muitas ramificações condicionais relacionadas, ele pode estar em uma classe de estratégia separada.
  • É necessário eliminar operadores condicionais.

O que o padrão faz?

Abaixo está a operação do padrão:

Estratégia (Strategy)

Elementos do padrão:

  • Estratégia (Strategy) - declara uma interface comum para todos os algoritmos suportados, que será usada pelo contexto para chamar o algoritmo definido pela estratégia concreta (ConcreteStrategy).
  • Estratégia Concreta (ConcreteStrategy) - o contexto implementa o algoritmo usando a interface do padrão.
  • Contexto (Context) - é criado pelo objeto da Estratégia Concreta, mantém uma referência ao objeto da estratégia e pode identificar a interface para fornecer à estratégia acesso aos seus dados.

Armadilhas:

  • O cliente deve estar ciente das estratégias.
  • Maior quantidade de classes.

Que problema o padrão resolve?

Consequências (vantagens) do uso do padrão:

  • Há possibilidade de reutilização, pois podemos identificar grupos de algoritmos relacionados que podem ser reutilizados no contexto.
  • Isso pode ser visto como mais uma maneira de suportar diferentes variações de comportamento em vez de criar subclasses.
  • Permite eliminar operadores condicionais como parte da estratégia.
  • Permite escolher entre diferentes implementações do mesmo comportamento.

Como utilizá-lo no MQL5?

Vamos declarar a interface Strategy para a declaração de classes e funções internas:

interface Strategy
  {
   void AlgorithmInterface(void);
  };

Declarar a classe 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 a classe ConcreteStrategyA

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

Declaramos a classe ConcreteStrategyB

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

Declaramos a classe ConcreteStrategyC

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

Abaixo está o código completo do padrão em um bloco:

//+------------------------------------------------------------------+
//|                                                     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 do Padrão (Template Method)

O padrão pode ser usado para identificar os componentes principais de um algoritmo dentro de uma operação e delegar certos passos a subclasses. Isso permite que as subclasses modifiquem esses passos sem alterar a estrutura geral do algoritmo.

Então, podemos usar esse padrão quando:

  • Temos um algoritmo e precisamos modificar apenas alguns passos dele, sem afetar a estrutura geral.
  • Precisamos implementar aspectos invariantes do algoritmo apenas uma vez e delegar a responsabilidade pela implementação do comportamento variável às subclasses.
  • Precisamos eliminar a duplicação de código, quando o comportamento comum às subclasses deve ser considerado e localizado na classe geral.
  • Precisamos controlar as extensões das subclasses.

O que o padrão faz?

Abaixo está a operação do padrão:

Método de template (Template Method)

Elementos do padrão:

  • Classe Abstrata (AbstractClass) - usada para indicar operações primitivas abstratas que devem ser definidas por subclasses específicas para implementar diferentes passos do algoritmo. Também pode ser usada para executar o método do padrão, que descreve a estrutura do algoritmo. O método chama tanto as operações primitivas quanto as operações especificadas na Classe Abstrata ou que pertencem a outros objetos.
  • Classe Definida (ConcreteClass) - usada para realizar as operações primitivas necessárias para executar os passos do algoritmo específico para a subclass.

Que problema o padrão resolve?

Vantagens e desvantagens:

  • O uso do padrão nos permite criar código reutilizável, pois é um método básico de reutilização de código, especialmente em bibliotecas de classes.
  • Como já mencionado, isso pode nos permitir modificar os passos do algoritmo sem mudar sua estrutura.

No entanto, é necessário considerar que todas as classes devem seguir o algoritmo sem exceções.

Como utilizá-lo no MQL5?

Declaramos a Classe Abstrata, usando a palavra-chave class

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

Declaramos a Classe Definida, usando a palavra-chave class

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

Abaixo está o código completo do padrão em um bloco:

//+------------------------------------------------------------------+
//|                                              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)

O padrão oferece a possibilidade de identificar uma nova operação sem afetar as classes dos elementos sobre os quais a operação é realizada. Podemos usar este padrão quando temos uma estrutura de objeto que contém muitas classes diferentes de objetos e interfaces, e precisamos realizar operações sobre esses objetos, ou temos muitas operações diferentes e não relacionadas a serem executadas nos objetos dentro da estrutura do objeto e há o desejo de evitar "contaminar" nossas classes com essas operações. Além disso, as classes que definem a estrutura do objeto raramente mudam, mas precisamos definir novas operações sobre a estrutura.

O que o padrão faz?

A diagramação abaixo mostra o princípio de funcionamento do padrão:

Visitante (Visitor)

Elementos do padrão:

  • Visitante (Visitor) - para cada classe ConcreteElement na estrutura do objeto, uma operação Visit (visita) é declarada. O nome e a assinatura da operação identificam de forma inequívoca a classe que solicita a visita do visitante. Essa configuração proporciona acesso direto ao elemento através de seu interface especial, permitindo que o visitante identifique a classe do elemento visitado.
  • Visitante Definido (ConcreteVisitor) - implementa cada operação anunciada pelo visitante. Cada operação realiza uma parte do algoritmo definido para a classe de objeto correspondente na estrutura. O Visitante Definido fornece contexto para o algoritmo e mantém seu estado local. Esse estado geralmente representa o acúmulo de resultados durante a iteração da estrutura.
  • Elemento (Element) - define a operação de recepção, que aceita um visitante como argumento.
  • Elemento Definido (ConcreteElement) - executa a operação de recepção, que também aceita um visitante como argumento.
  • Estrutura do Objeto (ObjectStructure) - pode listar seus elementos, oferecendo um interface de alto nível que permite ao visitante visitar os elementos. A estrutura pode ser composta ou representar uma coleção.

Que problema o padrão resolve?

De acordo com o que mencionamos sobre o padrão, ele tem as seguintes vantagens:

  • Permite adicionar facilmente novas operações.
  • Permite distinguir entre operações relacionadas e não relacionadas, unindo operações relacionadas e separadas não relacionadas.

Como utilizá-lo no MQL5?

Declare o interface do padrão

interface Visitor;

Declare a classe 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 a classe 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 a classe 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);
  }

Usamos a palavra-chave interface para definir VisitElementA e VisitElementB dentro

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

Declaramos a classe 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 a classe 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();
  }

Abaixo está o código completo do padrão em um bloco:

//+------------------------------------------------------------------+
//|                                                      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();
  }

Considerações finais

Lendo todos os artigos desta série, você será capaz de identificar todos os tipos de padrões de design (criacionais, estruturais e comportamentais). Definimos cada um deles, vimos como funcionam e quando podemos usá-los, que problemas a aplicação de cada padrão resolve e como podemos usar esses padrões em MQL5 para escrever código limpo, suportável, reutilizável e bem testado.

Revisamos os seguintes padrões:

  • Criacionais
    • Fábrica abstrata
    • Construtor
    • Método de fábrica
    • Protótipo
    • Singleton
  • Estrutural
    • Adaptador
    • Ponte
    • Compositor
    • Decorador
    • Fachada
    • Peso mosca
    • Proxy
  • Padrões comportamentais
    • Cadeia de responsabilidade
    • Comando
    • Intérprete
    • Iterador
    • Mediador
    • Lembrete
    • Observador
    • Estado
    • Método de template
    • Visitante

Você pode ler os demais artigos desta série clicando nos links abaixo:

Quero enfatizar novamente a importância de entender os padrões de projeto, pois eles podem ser muito úteis na criação de qualquer software. Então, recomendo que você leia mais literatura sobre este tópico. Abaixo, algumas referências úteis:

  • 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 série de artigos seja útil para você e ajude a melhorar suas habilidades de programação em geral e programação no MQL5 em particular. Na seção publicações você pode encontrar outros artigos meus, incluindo aqueles sobre programação em MQL5 e criação de sistemas de negociação com base em indicadores técnicos populares, como médias móveis, RSI, Bandas de Bollinger e MACD. Espero que eles também sejam úteis para você.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13876

Arquivos anexados |
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)
Algoritmos de otimização populacional: algoritmos de estratégias evolutivas (Evolution Strategies, (μ,λ)-ES e (μ+λ)-ES) Algoritmos de otimização populacional: algoritmos de estratégias evolutivas (Evolution Strategies, (μ,λ)-ES e (μ+λ)-ES)
Neste artigo, vamos falar sobre um grupo de algoritmos de otimização conhecidos como "Estratégias Evolutivas" (Evolution Strategies ou ES). Eles são alguns dos primeiros algoritmos que usam princípios de evolução para encontrar soluções ótimas. Vamos mostrar as mudanças feitas nas versões clássicas das ES, além de revisar a função de teste e a metodologia de avaliação dos algoritmos.
Ciência de dados e aprendizado de máquina (Parte 17): O dinheiro cresce em árvores? Florestas aleatórias no trading de forex Ciência de dados e aprendizado de máquina (Parte 17): O dinheiro cresce em árvores? Florestas aleatórias no trading de forex
Neste artigo, vamos desvendar os segredos da alquimia algorítmica, explorando a arte e precisão dos mercados financeiros. Você vai ver como as florestas aleatórias transformam dados em previsões e ajudam a navegar nas complexidades do mercado financeiro. Vamos entender o papel das florestas aleatórias com dados financeiros e ver se elas podem ajudar a aumentar os lucros.
Desenvolvendo um sistema de Replay (Parte 52): Complicando as coisas (IV) Desenvolvendo um sistema de Replay (Parte 52): Complicando as coisas (IV)
Neste artigo vamos fazer uma mudança no indicador de mouse a fim de poder efetuar a interação com o indicador de controle, já que a interação está sendo feita de forma errática.
Indicador de posições históricas no gráfico em forma de diagrama de lucro/prejuízo Indicador de posições históricas no gráfico em forma de diagrama de lucro/prejuízo
Vamos falar sobre como obter informações sobre posições fechadas usando o histórico de negociações. Vamos criar um indicador simples que mostra um diagrama aproximado de lucro/prejuízo das posições em cada barra.