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

Padrões de projeto no MQL5 (Parte 3): Padrões comportamentais 1

MetaTrader 5Negociação | 14 maio 2024, 09:49
170 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Introdução

O artigo dá continuidade à série sobre padrões de projeto na área de software. Nos dois artigos anteriores desta série, identificamos dois tipos desses padrões: criacionais e estruturais. Neste artigo, definiremos que são os padrões comportamentais e discutiremos como eles podem ser úteis na criação, compilação ou desenvolvimento de nosso software e como podemos aplicá-los no MQL5 para desenvolver nosso software para o MetaTrader 5, a fim de criar um software confiável, sustentável, reutilizável, bem testado e expansível.

Conceitos principais que serão usados no artigo:

Eu recomendo ler também os artigos sobre padrões criacionais e padrões estruturais para ter uma visão geral de um dos temas mais importantes no desenvolvimento de software, os padrões de projeto, pois eles podem ser muito úteis no desenvolvimento de aplicativos.

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 que você aplicar na prática com base neste artigo é feito exclusivamente por sua conta e risco, e o autor não garante nenhum resultado.

Padrões comportamentais

Neste artigo, vamos examinar o último tipo de padrão de projeto: o comportamental. Já sabemos que os padrões criacionais ajudam a criar um software ou sistema independente através da criação, compilação e representação de objetos, e os padrões estruturais podem ser usados para criar estruturas maiores usando os objetos e classes criados.

Neste artigo, exploraremos os padrões comportamentais, relacionados com a designação e distribuição de responsabilidades entre os objetos. Eles também definem como os objetos podem interagir entre si, e existem muitos padrões desse tipo, como por exemplo:

  • Cadeia de responsabilidade (Chain of Responsibility)
  • Comando (Command)
  • Intérprete (Interpreter)
  • Iterador (Iterator)
  • Mediador (Mediator)
  • Lembrete (Memento)
  • Observador (Observer)
  • Estado (State)
  • Estratégia (Strategy)
  • Método de template (Template Method)
  • Visitante (Visitor)

Como existem muitos padrões que não podem ser abordados em um único artigo, focaremos apenas nos cinco primeiros:

  • Cadeia de Responsabilidade: ajuda a aplicar a separação do remetente e do receptor, permitindo que mais de um objeto trate uma solicitação do remetente. Ela também ajuda a vincular a recepção dos objetos em cadeia e passar a solicitação por essa cadeia para completar o processamento do objeto.
  • Comando: permite definir parâmetros para clientes com diferentes solicitações, enfileirar ou registrar solicitações, e também suporta operações canceláveis após a encapsulação da solicitação em forma de objeto.
  • Intérprete: define a representação da gramática de uma linguagem específica e interpreta o necessário em forma de frases na linguagem.
  • Iterador: se precisamos de acesso sequencial aos elementos de um objeto composto, sem revelar sua representação básica, esse padrão fornece o método necessário.
  • Mediador: define como um conjunto de objetos interage através de um objeto encapsulado, e facilita a separação, permitindo-nos alterar a interação entre os objetos de forma independente.

Se você leu os dois artigos anteriores sobre padrões de projeto, já está familiarizado com a abordagem que usamos para descrever cada padrão:

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


Cadeia de responsabilidade (Chain of Responsibility)

Nesta seção, vamos explorar a cadeia de responsabilidade, descobrir o que ela pode fazer e como podemos usá-la no MQL5. Quando precisamos tratar uma solicitação de um cliente, e se temos muitos objetos que podem lidar com solicitações do cliente com base na responsabilidade de cada um, podemos usar este padrão para tratar esse caso.

Apesar das vantagens de usar este padrão, ele tem suas desvantagens:

  • Problema de eficiência no caso de cadeias longas.
  • Não há garantia de tratamento da solicitação, pois o solicitante não é especificado, portanto, a solicitação pode ser passada aos objetos na cadeia sem tratamento. Além disso, a solicitação não pode ser tratada se não tivermos uma cadeia adequadamente configurada.
O que o padrão faz?

O padrão pode ser útil para separar o remetente de qualquer solicitação e o receptor dessa solicitação, dando a vários objetos a capacidade de lidar com a solicitação. Isso é feito combinando os objetos receptores e passando a solicitação a todos para verificar qual deles pode lidar com a solicitação.

Abaixo está a estrutura do padrão:

Cadeia de Responsabilidade

Vamos considerar os elementos presentes no esquema acima:

  • Cliente (Client): o cliente inicia uma solicitação que será tratada por um objeto na cadeia.
  • Manipulador (Handler): define a interface para tratamento da solicitação. Ele também pode implementar um link sucessor.
  • Manipulador Concreto (Concrete Handler): o objeto que trata a solicitação, dependendo de sua responsabilidade. Ele tem acesso ao sucessor, que pode passá-lo adiante quando não consegue tratar a solicitação.

Que problema o padrão resolve?

Este padrão pode ser usado se o seguinte for aplicável:

  • Temos muitos objetos que podem lidar com solicitações.
  • Precisamos separar o remetente e o receptor.
  • Precisamos criar uma solicitação a um dos muitos objetos, sem mencionar o receptor.
  • Temos um conjunto dinamicamente especificado de objetos que podem lidar com a solicitação.

Assim, este padrão pode resolver o seguinte:

  • Redução da conectividade, pois ajuda a separar o remetente do receptor, o que significa permitir mudanças independentes.
  • Isso proporciona flexibilidade na atribuição e distribuição de responsabilidades entre os objetos.

Como usá-lo no MQL5?

Nesta seção, aprenderemos como utilizar o padrão no MQL5 para criar software eficaz para o MetaTrader 5. Aqui estão os passos para codificar a cadeia de responsabilidades no MQL5:

Declaração do escopo Chain_Of_Responsibility para incluir funções e variáveis do padrão usando a palavra-chave namespace

namespace Chain_Of_Responsibility

Declararemos o membro da classe Handler, que trata solicitações do cliente e pode implementar um link sucessor

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

Declararemos o membro da classe ConcreteHandler1, que trata solicitações pelas quais é responsável, ou passará a solicitação ao seu sucessor, se ele não puder tratá-la

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

Também declararemos a classe ConcreteHandler2 como um membro

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

Declararemos a classe cliente, que inicia uma solicitação a um manipulador específico na cadeia

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

A função de início do cliente consiste em enviar a solicitação para a cadeia para processamento ou passagem ao sucessor

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

Abaixo está o código completo para usar o padrão de cadeia de responsabilidades no MQL5 em um bloco

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

O padrão Command também é conhecido como Ação (Action) ou Transação (Transaction). Esse padrão ajuda a encapsular uma solicitação em um objeto, e isso nos permite definir parâmetros para diferentes solicitações, sem alterar o remetente ou o receptor, o que significa que a separação agora se aplica entre o remetente, o processador e o destinatário. Isso é muito útil quando as classes possuem funcionalidades extensivas. O padrão também suporta operações de cancelamento.

Como na maioria dos casos, ele tem suas desvantagens:

  • Geralmente é usado em combinação com outros padrões.
  • Isso leva a um grande número de classes e objetos para lidar com diferentes comandos ou solicitações.
  • Criar um objeto para cada comando contradiz o design orientado a objetos.
O que o padrão faz?

Simplesmente, ele cria um iniciador encapsulado para receber o comando e enviá-lo ao receptor.

Esquema do padrão:

Comando

Como podemos ver, os componentes do padrão incluem:

  • Command (Comando): declara a interface para realizar uma operação.
  • ConcreteCommand (Comando Concreto): cria uma ligação entre o receptor e a ação ou comando. A execução é realizada chamando a operação apropriada no receptor.
  • Client (Cliente): cria o ConcreteCommand e define seu receptor.
  • Invoker (Iniciador): recebe a comando para executar a solicitação.
  • Receiver (Receptor): identifica o método de operações relacionadas à execução da solicitação. Pode ser qualquer classe.

Que problema o padrão resolve?

  • Podemos usar este padrão nos seguintes casos:
  • Ao definir parâmetros para objetos dependendo da ação realizada.
  • Ao definir parâmetros para objetos dependendo da ação realizada. realizar operações como especificação, enfileiramento e execução em momentos diferentes.
  • Ao precisar de algo que possa ser usado para suportar a operação de cancelamento.
  • Em caso de falhas no sistema, precisamos de suporte para registrar as alterações que, neste caso, podem ser reaplicadas.
  • Quando precisamos de operações de alto nível construídas sobre primitivas como estrutura do sistema.

Como usá-lo no MQL5?

Nesta seção, vamos explorar o método que pode ser usado para implementar o padrão Command no MQL5. Para isso, precisamos seguir os passos abaixo:

Declaramos nosso espaço Command para especificar funções, variáveis, classes etc., usando a palavra-chave namespace.

namespace Command

Declaramos a classe Receiver como membro, que define o método para executar operações de solicitação.

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

Declaramos a classe Command como membro para declarar a interface de operação.

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 a classe ConcreteCommand como membro para criar uma conexão entre o receptor e a ação ou comando e implementar o método execute() após chamar a operação do 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 a classe Invoker como membro para receber o comando para executar a solicitação.

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

Declaramos a classe cliente como membro para criar um comando concreto, configurar seu receptor e iniciá-lo.

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

O código completo em um único bloco é mostrado abaixo.

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

Outro padrão comportamental que pode ser usado para ajudar a estabelecer a interação entre objetos através de uma linguagem específica e definir a representação de regras ou gramática. Essa representação pode então ser usada para explicar e interpretar o conteúdo da linguagem.

Desafios possíveis:

  • Complexidade das regras ou gramática. Quanto maior a complexidade, mais difícil é a manutenção.
  • O padrão pode ser usado em situações específicas.
O que o padrão faz?

O padrão nos ajuda a descrever como definir a gramática de uma linguagem, apresentar o conteúdo dessa linguagem e obter a interpretação desse conteúdo.

Esquema do padrão:

Intérprete

Vamos considerar os elementos presentes no esquema acima:

  • AbstractExpression (Expressão Abstrata): declara a operação de interpretação abstrata (contexto).
  • TerminalExpression (Expressão Terminal): implementa a operação de interpretação associada aos símbolos terminais na gramática, criando uma instância como requisito para cada símbolo terminal no conteúdo.
  • NonterminalExpression (Expressão Não Terminal): para cada regra na gramática, é necessário uma classe, que suporta variáveis de instância de AbstractExpression para cada regra. Este elemento implementa a operação de interpretação de símbolos não terminais na gramática.
  • Context (Contexto): contém informações globais para o intérprete.
  • Client (Cliente): constrói uma árvore sintática abstrata para representar o conteúdo na linguagem que precisamos definir pela gramática e invoca a operação de interpretação.

Que problema o padrão resolve?

Como já mencionado, o padrão pode ser usado quando temos uma linguagem que precisamos interpretar, e podemos definir ou representar conteúdo nessa linguagem.

Abaixo estão os casos mais adequados para usar o padrão:

  • Temos uma gramática de linguagem simples porque, se a gramática for complexa, a hierarquia de classes se tornará grande e isso pode levar a uma situação incontrolável.
  • O fator eficiência no interpretador não é tão importante.

Assim, usando esse padrão, podemos obter as seguintes vantagens:

  • Usar esse padrão simplifica a atualização ou expansão da gramática por meio de herança.
  • O padrão também facilita a implementação da gramática.
  • Além disso, simplifica a adição de novas maneiras de interpretar expressões.

Como usá-lo no MQL5?

Nesta seção, apresentaremos um método simples de codificação ou uso desse tipo de padrão. Abaixo estão os passos para usar o interpretador no MQL5:

Declaramos a área do interpretador que usaremos para definir e declarar nossas funções, variáveis e classes usando a palavra-chave namespace.

namespace Interpreter

Declaramos a classe context como um membro.

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)
  {
  }

Declaramos a classe Abstract como um membro.

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

Declaramos a classe TerminalExpression como um membro para implementar o método de interpretação.

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

Declaramos a classe NonterminalExpression como um membro.

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

Declaramos o cliente como um membro.

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

A seguir, o código completo para usar o interpretador em um único bloco:

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

Iterador é um dos padrões de comportamento que define um método de interação ou ligação entre objetos. O padrão ajuda a garantir a presença de um objeto agregado, como uma lista, ou nos fornece um método para acessar sequencialmente os elementos sem revelar seus detalhes de representação ou estrutura interna. Também é conhecido como Cursor.

Desvantagens:

  • Se tivermos uma coleção, não há acesso ao seu índice.
  • Estado unidirecional em alguns casos, quando precisamos, por exemplo, voltar ao anterior (em algumas linguagens existem soluções para este problema).
  • Em alguns casos, será mais rápido criar um índice e percorrê-lo do que usar o padrão.
O que o padrão faz?

O padrão pode suportar variantes no caso de objetos agregados complexos que podem ser navegados de maneiras diferentes, já que o algoritmo de navegação é facilmente atualizado substituindo a instância do iterador e definindo subclasses para suportar as navegações atualizadas. Isso simplifica a interface do objeto agregado. O rastreamento do estado de navegação do iterador nos permite lidar com múltiplas navegações simultaneamente.

Esquema do padrão:

Iterador

No esquema, temos os seguintes elementos:

  • Iterator: define a interface que pode ser usada para acessar e mover-se pelos elementos.
  • ConcreteIterator (Iterador Definido): permite implementar a interface do iterador e rastrear a navegação agregada.
  • Aggregate (Complexo): ajuda a identificar a interface que pode ser usada para criar um iterador do objeto.
  • ConcreteAggregate (Complexo Definido): implementa a interface do iterador para obter como retorno uma instância do ConcreteIterator apropriado.

Que problema o padrão resolve?

O padrão de iterador pode ser usado nos seguintes casos:

  • Se precisarmos acessar o conteúdo de um objeto agregado, mas não queremos revelar sua representação interna.
  • Se tivermos várias navegações de objetos agregados, precisamos suportá-las.
  • Se tivermos diferentes estruturas agregadas e precisarmos fornecer uma única interface para sua navegação.

Como usá-lo no MQL5?

Vamos considerar como usar este padrão no MQL5, executando as seguintes etapas:

Definimos ERRITERAOR-UT-OF-BOUNDS usando o pré-processador #define

#define ERR_ITERATOR_OUT_OF_BOUNDS 1

Usamos a palavra-chave template e declaramos T como CurrentItem na interface Iterator definida

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

Além disso, usamos a palavra-chave template e declaramos T como operador na interface Aggregate definida

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

Implementação da interface do iterador e rastreamento da navegação agregada como a posição atual após a declaração da classe 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];
  }

Implementação da interface de criação de iteradores para obter uma instância do ConcreteIterator adequado 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);
  }

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

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

Mais um padrão de projeto comportamental que pode ser usado para definir como os objetos podem interagir entre si. O mediador pode ser usado quando temos um conjunto de objetos e precisamos definir um objeto encapsulado para descrever como esse conjunto de objetos pode interagir. Ele também aplica a separação, o que pode ser útil e nos permite mudar a interação dos objetos independentemente.

Desafios possíveis:

  • Um mediador para tudo.
  • Usado com outros padrões.
O que o padrão faz?

O padrão ajuda a estabelecer um método de interação entre objetos sem mencionar explicitamente cada objeto. Isso permite aplicar uma separação entre os objetos. Também pode ser usado como um roteador e para gerenciar a interação.

Esquema do padrão:

Mediador

Mediador (2)

Como podemos ver, os componentes do padrão incluem:

  • Mediator (Медиатор): define a interface para comunicação com os objetos colegas.
  • ConcreteMediator (Mediador Definido): implementa a cooperação coordenando os objetos colegas. Seus colegas são conhecidos e podem ser suportados usando o ConcreteMediator.
  • Colleague classes (Classes Colegas): cada classe colega conhece o mediador do objeto. Além disso, cada colega pode se comunicar com seu mediador a qualquer momento que essa comunicação seja necessária, ou conversar com outro colega.

Que problema o padrão resolve?

O padrão pode ser usado nos seguintes casos:

  • Temos um conjunto de objetos que podem interagir entre si depois de definir um método de interação, mas a própria interação é complicada.
  • Temos dificuldades em reutilizar um objeto porque ele interage com outros objetos.
  • Precisamos configurar um comportamento que é distribuído entre classes, sem criar muitas subclasses.

Então, podemos dizer que o padrão:

  • Ajuda a limitar as subclasses.
  • Ajuda a separar os colegas.
  • Simplifica os protocolos de objeto.
  • A interação entre os objetos é abstrata.
  • O gerenciamento é centralizado.

Como usá-lo no MQL5?

Vamos considerar como usar este padrão no MQL5, executando as seguintes etapas:

Usamos a palavra-chave interface para criar a interface Colleague (colega)

interface Colleague
  {
   void Send(string message);
  };
Usamos a palavra-chave interface para criar a interface do mediador
interface Mediator
  {
   void Send(string message,Colleague& colleague);
  };

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

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

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

Abaixo está o código completo em um único bloco

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


Conclusão

Exploramos outro tipo de padrões de projeto, que são um dos temas mais importantes em programação e desenvolvimento de software. Examinamos alguns padrões de comportamento e definimos o que eles representam e como podem ser úteis para criar software reutilizável, extensível, sustentável e testável. Também estudamos o que cada padrão pode fazer, suas potenciais desvantagens e problemas que podem resolver, bem como como podemos usar cada padrão no MQL5 para criar sistemas de negociação eficazes para o MetaTrader 5.

Neste artigo, analisamos os seguintes padrões de comportamento:

  • Cadeia de responsabilidades (Chain of responsibilities)
  • Comando (Command)
  • Intérprete (Interpreter)
  • Iterador (Iterator)
  • Mediador (Mediator)

Se esta for a primeira parte da série que você leu, também recomendo que leia meus artigos "Padrões de projeto no MQL5 (Parte I): Padrões criacionais (Creational Patterns)" e "Padrões de projeto no MQL5 (Parte 2): Padrões estruturais", para aprender sobre outros tipos de padrões de projeto. Espero que você os ache úteis.

Abaixo estão algumas obras úteis para um estudo mais aprofundado de padrões de projeto:

  • 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

Se você quiser ler mais artigos sobre a criação de sistemas de negociação para o MetaTrader 5 usando os indicadores técnicos mais populares, você pode ler outros dos meus artigos. Os links necessários podem ser encontrados na seção "Publicações" do meu perfil. Espero que você os encontre úteis para sua negociação e que eles ajudem você a melhorar seus resultados tanto na negociação quanto no desenvolvimento.

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

Arquivos anexados |
Command.mqh (4.04 KB)
Interpreter.mqh (4.83 KB)
Iterator.mqh (2.31 KB)
Mediator.mqh (1.72 KB)
Anotação de dados na análise de série temporal (Parte 4): Decomposição da interpretabilidade usando anotação de dados Anotação de dados na análise de série temporal (Parte 4): Decomposição da interpretabilidade usando anotação de dados
Esta série de artigos apresenta várias técnicas destinadas a rotular séries temporais, técnicas essas que podem criar dados adequados à maioria dos modelos de inteligência artificial (IA). A rotulação de dados (ou anotação de dados) direcionada pode tornar o modelo de IA treinado mais alinhado aos objetivos e tarefas do usuário, melhorar a precisão do modelo e até mesmo ajudar o modelo a dar um salto qualitativo!
Escrevemos o primeiro modelo de caixa de vidro (Glass Box) em Python e MQL5 Escrevemos o primeiro modelo de caixa de vidro (Glass Box) em Python e MQL5
Os modelos de aprendizado de máquina são difíceis de interpretar, e entender o motivo pelo qual os modelos não atendem às nossas expectativas pode ajudar muito a alcançar o resultado desejado ao usar esses métodos modernos. Sem um entendimento abrangente do funcionamento interno do modelo, pode ser difícil identificar erros que prejudicam o desempenho. Nesse processo, podemos dedicar tempo a criar funções que não impactam na qualidade da previsão. No final, por melhor que seja o modelo, perdemos todos os seus principais benefícios devido a nossos próprios erros. Felizmente, existe uma solução complexa, mas bem desenvolvida, que permite ver claramente o que está acontecendo sob o capô do modelo.
Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 08): Perceptrons Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 08): Perceptrons
Os perceptrons, redes com uma única camada oculta, podem ser um bom suporte para aqueles familiarizados com os fundamentos do trading automático e que desejam mergulhar nas redes neurais. Vamos examinar passo a passo como eles podem ser implementados no conjunto de classes de sinais, que faz parte das classes do Assistente MQL5 para EAs.
Desenvolvendo um sistema de Replay (Parte 49): Complicando as coisas (I) Desenvolvendo um sistema de Replay (Parte 49): Complicando as coisas (I)
Aqui neste artigo iremos complicar um pouco as coisa. Fazendo uso do que foi visto nos artigos anteriores, iremos começar a liberar o arquivo de Template, para que o usuário possa fazer uso de um template pessoal. No entanto, irei fazer as mudanças aos poucos, visto que também irei modificar o indicador a fim de proporcionar um alivio ao MetaTrader 5.