English 中文 Español Deutsch 日本語 Português
preview
Шаблоны проектирования в программировании на MQL5 (Часть 3): Поведенческие шаблоны 1

Шаблоны проектирования в программировании на MQL5 (Часть 3): Поведенческие шаблоны 1

MetaTrader 5Трейдинг | 4 апреля 2024, 16:25
676 5
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Введение

Статья продолжает серию о шаблонах проектирования в области программного обеспечения. В двух предыдущих статьях этой серии мы определили два типа таких шаблонов - порождающие и структурные. В этой статье мы дадим определение поведенческим шаблонам, а также рассмотрим, как они могут быть полезны при создании, сборке или разработке нашего программного обеспечения и как мы можем использовать их в MQL5 для создания нашего программного обеспечения для MetaTrader 5, чтобы создать надежное, поддерживаемое, многоразовое, хорошо протестированное и расширяемое программное обеспечение.

Основные понятия, которые будут использованы в статье:

Я рекомендую прочитать также и статьи о порождающих и структурных шаблонах, чтобы получить общее представление об одной из наиболее важных тем в разработке программного обеспечения - шаблонах проектирования, поскольку они могут быть очень полезны в разработке приложений.

Внимание! Все содержание настоящей статьи предоставляется "как есть", предназначено только для целей обучения и не является торговой рекомендацией. Статья не несет в себе каких-либо гарантий результатов. Все, что вы применяете на практике на основе этой статьи, вы делаете исключительно на свой страх и риск, автор не гарантирует никаких результатов.

Поведенческие шаблоны

В этой статье мы рассмотрим последний тип шаблонов проектирования - поведенческие шаблоны. Мы уже знаем, что порождающие шаблоны помогают создавать независимое программное обеспечение или систему путем создания, составления и представления объектов, а структурные шаблоны можно использовать для создания более крупных структур с использованием созданных объектов и классов.

В этой статье мы рассмотрим поведенческие шаблоны, связанные с назначением и распределением обязанностей между объектами. Они также определяют, как объекты могут взаимодействовать друг с другом, и существует множество шаблонов этого типа, например следующие:

  • Цепочка ответственности (Chain of responsibility)
  • Команда (command)
  • Интерпретатор (Interpreter)
  • Итератор (Iterator)
  • Медиатор (Mediator)
  • Напоминание (Memento)
  • Наблюдатель (Observer)
  • Состояние (State)
  • Стратегия (Strategy)
  • Метод шаблона (Template Method)
  • Посетитель (Visitor)

Поскольку существует множество шаблонов, которые невозможно охватить в одной статье, мы сосредоточимся только на первых пяти:

  • Цепочка ответственности - помогает применить разделение отправителя и получателя, давая возможность более чем одному объекту обрабатывать запрос от отправителя. Она также помогает связать получение объектов в цепочку и передать запрос по этой цепочке для завершения обработки объекта.
  • Команда - дает разрешение устанавливать параметры клиентам с различными запросами, ставить в очередь или регистрировать запросы, а также поддерживает отменяемые операции после инкапсуляции запроса в виде объекта.
  • Интерпретатор - определяет представление грамматики определенного языка, интерпретирует то, что необходимо в виде предложений в языке.
  • Итератор - если нам нужен последовательный доступ к элементам составного объекта, не раскрывая его базовое представление, этот шаблон помогает получить необходимый метод.
  • Медиатор - определяет, как набор объектов взаимодействует через инкапсулированный объект, и способствует разделению, позволяя нам независимо изменять взаимодействие объектов.

Если вы прочитали две предыдущие статьи о шаблонах проектирования, вы уже знакомы с подходом, который мы используем для описания каждого шаблона:

  • Что делает шаблон?
  • Какую проблему решает шаблон?
  • Как его использовать в MQL5?


Цепочка ответственности (Chain of responsibility)

В этом разделе мы рассмотрим цепочку ответственности, узнаем, что она может делать и решим, как мы можем использовать ее в MQL5. Когда нам нужно обработать запрос от клиента, и если у нас есть много объектов, которые могут обрабатывать запросы клиента, исходя из ответственности каждого, мы можем использовать этот шаблон для обработки этого случая.

Несмотря на преимущества использования этого шаблона, у него есть и подводные камни:

  • Проблема эффективности в случае длинных цепочек.
  • Нет гарантии обработки запроса, поскольку у запроса не указан получатель, поэтому этот запрос можно передать объектам в цепочке без обработки. К тому же, запрос не может быть обработан, если у нас нет правильно настроенной цепочки.
Что делает шаблон?

Шаблон может быть полезен для разделения отправителя любого запроса и получателя этого запроса, предоставляя возможность множеству объектов обрабатывать запрос. Это происходит путем объединения принимающих объектов и передачи запроса всем, чтобы проверить, какой из них может обработать запрос.

Ниже приведена структура шаблона:

ЦЕПОЧКА ОТВЕТСТВЕННОСТИ

Рассмотрим элементы, присутствующие на схеме выше:

  • Client (Клиент) - клиент инициирует запрос, который будет обработан объектом в цепочке.
  • Handler (Обработчик) - определяет интерфейс для обработки запроса. Он также может реализовать ссылку-преемник.
  • ConcreteHandler (определенный обработчик) - объект, который обрабатывает запрос в зависимости от своей ответственности. У него есть доступ к преемнику, который может передать его, когда он не может обработать запрос.

Какую проблему решает шаблон?

Этот шаблон можно использовать, если применимо следующее:

  • У нас есть много объектов, которые могут обрабатывать запросы.
  • Нам нужно разделить отправителя и получателя.
  • Нам нужно создать запрос к одному из множества объектов, не упоминая получателя.
  • У нас есть динамически указанный набор объектов, которые могут обработать запрос.

Итак, этот шаблон может решить следующее:

  • Уменьшение связанности, поскольку это помогает разделить отправителя и получателя, что означает применение независимых изменений.
  • Это обеспечивает гибкость при назначении и распределении обязанностей между объектами.

Как его использовать в MQL5?

В этом разделе мы узнаем, как использовать шаблон в MQL5 для создания эффективного программного обеспечения для MetaTrader 5. Ниже приведены шаги для кодирования цепочки ответственности в MQL5:

Объявление области Chain_Of_Responsibility для включения в нее функций и переменных шаблона с помощью ключевого слова namespace

namespace Chain_Of_Responsibility

Объявим участника класса Handler, который обрабатывает запросы от клиента и может реализовать ссылку-преемник

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

Объявим участника класса ConcreteHandler1, который обрабатывает запросы, за которые он отвечает, или передадим запрос его преемнику, если он может его обработать

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

Класс ConcreteHandler2 тоже объявим участником

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

Объявим клиентский класс, который инициирует запрос к конкретному обработчику в цепочке

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

Ниже приведен полный код использования шаблона цепочки ответственности в MQL5 в одном блоке

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


Команда (command)

Шаблон Command также известен как Действие (Action) или Транзакция (Transaction). Шаблон помогает инкапсулировать запрос в объект, и это позволяет нам устанавливать параметры для разных запросов, не меняя отправителя или получателя, что означает, что теперь между отправителем, процессором и получателем применяется разделение. Это очень полезно, когда классы обладают обширным функционалом. Шаблон также поддерживает операции отмены.

Как и в большинстве случаев, у шаблона есть свои недостатки:

  • Обычно он используется в сочетании с другими шаблонами.
  • Это приводит к большому количеству классов и объектов для обработки различных команд или запросов.
  • Создание объекта для каждой команды противоречит объектно-ориентированному проектированию.
Что делает шаблон?

Проще говоря, он создает инкапсулированный инициатор для получения команды и отправки ее получателю.

Схема шаблона:

КОМАНДА

Как видим, в состав шаблона входят:

  • Command (Команда) - объявляет интерфейс для выполнения операции.
  • ConcreteCommand (Определенная команда) - создает связь между получателем и действием или командой. Выполнение реализуется путем вызова соответствующей операции на получателе.
  • Client (Клиент) - создает ConcreteCommand и устанавливает его получателя.
  • Invoker (Инициатор) - получает команду на выполнение запроса.
  • Receiver (Получатель) - идентифицирует метод операций, связанных с выполнением запроса. Может быть любым классом.

Какую проблему решает шаблон?

  • Мы можем использовать этот шаблон в следующих случаях:
  • Нам нужно установить параметры для объектов в зависимости от выполняемого действия.
  • Нам нужно выполнить такие операции, как спецификация, постановка в очередь и выполнение в разное время.
  • Нам нужно что-то, что можно использовать для поддержки операции отмены.
  • В случае сбоя в системе нам нужна поддержка протоколирования изменений, которые в этом случае можно будет применить повторно.
  • Нам нужны высокоуровневые операции, построенные на примитивных в виде структуры системы.

Как его использовать в MQL5?

В этом разделе мы рассмотрим метод, который можно использовать для использования шаблона команды в MQL5. Для этого необходимо выполнить следующие шаги:

Объявим наше пространство Command для указания функций, переменных, классов и т. д. с помощью ключевого слова namespace

namespace Command

Объявим класс Receiver в качестве участника, который определяет метод выполнения операций запроса

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

Объявим класс Command в качестве участника для объявления интерфейса операции

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

Объявим класс ConcreteCommand в качестве участника для создания связи между получателем и действием или командой и реализации метода execute() после вызова операции получателя

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

Объявим класс Invoker как участника для получения команды на выполнение запроса

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

Полный код в одном блоке представлен ниже

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


Интерпретатор (Interpreter)

Еще один поведенческий шаблон, который можно использовать, чтобы помочь установить взаимодействие между объектами через данный язык и определить представление правил или грамматики. Затем это представление можно использовать для объяснения и интерпретации содержания языка.

Возможные подводные камни:

  • Сложность правил или грамматики. Чем выше сложность, тем больше трудностей в поддержке.
  • Шаблон можно использовать в конкретных ситуациях.
Что делает шаблон?

Шаблон помогает нам описать, как определить грамматику языка, представить содержание этого языка и получить интерпретацию этого содержания.

Схема шаблона:

Интерпретатор

Рассмотрим элементы, присутствующие на схеме выше:

  • AbstractExpression (Абстрактное выражение) - объявляет операцию абстрактной интерпретации (контекста).
  • TerminalExpression (Терминальное выражение) - реализует операцию интерпретации, связанную с символами терминала в грамматике, создавая экземпляр в качестве требования для каждого символа терминала в содержании.
  • NonterminalExression (Нетерминальное выражение) - для каждого правила в грамматике требуется один класс, он поддерживает переменные экземпляров AbstractExpression для каждого правила. Элемент реализует операцию интерпретации нетерминальных символов в грамматике.
  • Context (Контекст) - содержит глобальную информацию для интерпретатора.
  • Client (Клиент) - строит абстрактное синтаксическое дерево для представления содержимого на языке, который нам нужен для определения грамматикой, и вызывает операцию интерпретации.

Какую проблему решает шаблон?

Как уже было сказано, шаблон можно использовать, когда у нас есть язык, который нам нужно интерпретировать, и мы можем определять или представлять контент на этом языке.

Ниже приведены наиболее подходящие случаи для использования шаблона:

  • У нас простая грамматика языка, потому что, если грамматика сложна, иерархия классов станет большой, и это может привести к неуправляемому состоянию.
  • Фактор эффективности в интерпретаторе не так важен.

Итак, используя этот шаблон, мы можем получить следующие преимущества:

  • Использование этого шаблона упрощает обновление или расширение грамматики с помощью наследования.
  • Шаблон также упрощает реализацию грамматики.
  • Шаблон упрощает добавление новых способов интерпретации выражений.

Как его использовать в MQL5?

В этой разделе мы представим простой метод кодирования или использования шаблона этого типа. Ниже приведены шаги по использованию интерпретатора в MQL5:

Объявим область интерпретатора, которую мы будем использовать для определения и объявления наших функций, переменных и классов, используя ключевое слово namespace.

namespace Interpreter

Объявим класс context в качестве участника

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

Объявим класс Abstract в качестве участника

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

Объявим класс TerminalExpression в качестве участника для реализации метода интерпретации

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

Объявим класс NonterminalExpression в качестве участника

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

Ниже представлен полный код использования интерпретатора в виде единого блока

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


Итератор (Iterator)

Итератор - один из шаблонов поведения, определяющих метод взаимодействия или связи между объектами. Шаблон помогает обеспечить присутствие совокупного объекта, такого как список, или дает нам метод для последовательного доступа к элементам, не раскрывая его основные детали представления или внутреннюю структуру. Он также известен как Курсор (Cursor).

Подводные камни:

  • Если у нас есть коллекция, доступа к ее индексу нет.
  • Однонаправленное состояние в некоторых случаях, когда нам нужно, например, перейти к предыдущему (в некоторых языках для этой проблемы есть решения).
  • В некоторых случаях будет быстрее создать индекс и пройтись по нему, чем использовать шаблон.
Что делает шаблон?

Шаблон может поддерживать варианты в случае сложных совокупных объектов, которые можно проходить по-разному, поскольку алгоритм обхода легко обновить, заменив экземпляр итератора и определив подклассы для поддержки обновленных обходов. Это упрощает интерфейс совокупного объекта. Отслеживание состояния обхода итератора позволяет нам одновременно обрабатывать множество обходов.

Схема шаблона:

ИТЕРАТОР

На схеме имеются следующие элементы:

  • Iterator (Иетратор) - определяет интерфейс, который можно использовать для доступа к элементам и перемещения по ним.
  • ConcreteIterator (Определенный итератор) - позволяет реализовать интерфейс итератора и отслеживает совокупный обход.
  • Aggregate (Комплекс) - помогает идентифицировать интерфейс, который можно использовать для создания итератора объекта.
  • ConcreteAggregate (Определенный комплекс) - реализует интерфейс итератора, чтобы получить в качестве возврата экземпляр подходящего ConcreteIterator.

Какую проблему решает шаблон?

Шаблон итератора можно использовать в следующих случаях:

  • Если нам нужно получить доступ к содержимому совокупного объекта, но нам не нужно раскрывать его внутреннее представление.
  • Если у нас много обходов совокупных объектов, нам нужно это поддерживать.
  • Если у нас разные совокупные структуры и нам необходимо предоставить единый интерфейс для их обхода.

Как его использовать в MQL5?

Рассмотрим, как использовать этот шаблон в MQL5, выполнив следующие шаги:

Defining ERRITERAOR-UT-OF-BOUNDS by using the preprocessor #define

#define ERR_ITERATOR_OUT_OF_BOUNDS 1

Используем ключевое слово шаблона и объявим T как CurrentItem в определенном интерфейсе Iterator

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

Кроме того, используем ключевое слово template и объявим T в качестве оператора в определенном интерфейсе Aggregate

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

Реализация интерфейса итератора и отслеживание совокупного обхода как текущей позиции после объявления класса 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];
  }

Реализация интерфейса создания итератора для получения экземпляра подходящего конкретного итератора в качестве возвращаемого значения

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

Ниже приведен полный код шаблона в одном блоке

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


Медиатор (Mediator)

Еще один шаблон поведенческого проектирования, который можно использовать для определения того, как объекты могут взаимодействовать друг с другом. Медиатор можно использовать, когда у нас есть набор объектов и нам нужно определить инкапсулированный объект, чтобы описать, как этот набор объектов может взаимодействовать. Он также применяет разделение, что может быть полезно и позволяет нам независимо изменять взаимодействие объектов.

Возможные подводные камни:

  • Один медиатор для всего.
  • Используется с другими шаблонами.
Что делает шаблон?

Шаблон помогает установить метод взаимодействия между объектами без явного упоминания каждого объекта. Это позволяет применить разделение между объектами. Его также можно использовать в качестве маршрутизатора и для управления взаимодействием.

Схема шаблона:

Медиатор

Медиатор (2)

Как видим, в состав шаблона входят:

  • Mediator (Медиатор) - определяет интерфейс для связи с объектами коллег.
  • ConcreteMediator (Определенный медиатор) - реализует сотрудничество путем координации объектов коллег. Его коллеги известны и могут поддерживаться с помощью ConcreteMediator.
  • Colleague classes (Классы коллег) - медиатор объекта известен каждому классу коллег. Кроме того, каждый коллега может связаться со своим медиатором в любой момент, когда это общение необходимо, или пообщаться с другим коллегой.

Какую проблему решает шаблон?

Шаблон можно использовать в следующих случаях:

  • У нас есть набор объектов, которые могут взаимодействовать друг с другом после определения способа взаимодействия, но само взаимодействие усложнено.
  • У нас возникают трудности с повторным использованием объекта, поскольку он взаимодействует с другими объектами.
  • Нам нужно настроить поведение, которое распределяется между классами, не создавая множество подклассов.

Итак, мы можем сказать, что шаблон:

  • Помогает ограничить подклассы.
  • Помогает разделить коллег.
  • Упрощает объектные протоколы.
  • Взаимодействие между объектами абстрактно.
  • Управление централизовано.

Как его использовать в MQL5?

Рассмотрим, как использовать этот шаблон в MQL5, выполнив следующие шаги:

Используем ключевое слово interface для создания интерфейса Colleague (коллега)

interface Colleague
  {
   void Send(string message);
  };
Используем ключевое слово interface для создания интерфейса медиатора
interface Mediator
  {
   void Send(string message,Colleague& colleague);
  };

Объявим класс 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);
  }

Объявим класс 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);
  }

Объявим класс 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);
  }

Ниже представлен полный код в одном блоке

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


Заключение

Мы рассмотрели еще один тип шаблонов проектирования, которые являются одной из наиболее важных тем в программировании и разработке программного обеспечения. Мы рассмотрели некоторые поведенческие шаблоны и определили, что они собой представляют и как они могут быть полезны для создания многоразового, расширенного, поддерживаемого и протестированного программного обеспечения. Мы также изучили, что может делать каждый шаблон, его потенциальные недостатки и проблемы, которые можно решить с его помощью, а также то, как мы можем использовать каждый шаблон в MQL5 для создания эффективных торговых систем для MetaTrader 5.

В этой статье мы рассмотрели следующие поведенческие шаблоны:

  • Цепочка ответственности (Chain of responsibilities)
  • Команда (command)
  • Интерпретатор (Interpreter)
  • Итератор (Iterator)
  • Медиатор (Mediator)

Если это первая статья серии, которую вы прочли, также рекомендую прочитать мои статьи "Шаблоны проектирования в MQL5 (Часть I): Порождающие шаблоны (Creational Patterns)" и "Шаблоны проектирования в MQL5 (Часть 2): Структурные шаблоны", чтобы узнать о других типах шаблонов проектирования. Надеюсь, вы найдете их полезными.

Ниже приведены некоторые полезные работы для углубленного изучения шаблонов проектирования:

  • 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

Если вы хотите прочитать больше статей о создании торговых систем для MetaTrader 5 с использованием самых популярных технических индикаторов, вы можете прочитать другие мои статьи. Нужные ссылки можно найти в разделе "Публикации" моего профиля. Надеюсь, что вы найдете их полезными для своей торговли и они позволят вам улучшить ваши результаты как в торговле, так и в разработке.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/13796

Прикрепленные файлы |
Command.mqh (4.04 KB)
Interpreter.mqh (4.83 KB)
Iterator.mqh (2.31 KB)
Mediator.mqh (1.72 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (5)
Maxim Kuznetsov
Maxim Kuznetsov | 4 апр. 2024 в 17:22

после 

дальше можно не читать

Rashid Umarov
Rashid Umarov | 5 апр. 2024 в 08:51
Maxim Kuznetsov #:

после 

дальше можно не читать

Это перевод с английского оригинала.


Можете мысленно заменить на "обработчик". Ну или просто не читать, а писать свои статьи. 

Статья по теме https://habr.com/ru/articles/113995/

Maxim Kuznetsov
Maxim Kuznetsov | 5 апр. 2024 в 17:45
Rashid Umarov #:

Это перевод с английского оригинала.


Можете мысленно заменить на "обработчик". Ну или просто не читать, а писать свои статьи. 

Статья по теме https://habr.com/ru/articles/113995/

не о переводе речь.. в статье текста практически и нет, поэтому к коду предвзятое строгое отношение

код со скриншота имеет у вас шанс пройти code-review ?

а поводу "писать свои" - вы в курсе дел, я предлагал серию про использование gcc и окружение msys2, но оказалось что кроме MSVC нельзя

trampampam
trampampam | 5 апр. 2024 в 21:21
Maxim Kuznetsov #:

А как по Вашему "правильно"?

Denis Kirichenko
Denis Kirichenko | 22 апр. 2024 в 12:52
template<typename T>
void ConcreteIterator::Next(void)
  {
   m_current++;
   if(!IsDone())
     {
     }
  }


Это вообще для чего? Посмотрел материал по итераторам, есть такие варианты:

1)

template<typename T>
void ConcreteIterator::Next(void)
  {
   m_current++;
  }

2) 

template<typename T>
void ConcreteIterator::Next(void)
  {   
   if(!IsDone())
     {
       m_current++;
     }
  }
Нестационарные процессы и ложная регрессия Нестационарные процессы и ложная регрессия
Статья призвана продемонстрировать факт появления ложной регрессии при попытках применить регрессионный анализ к нестационарным процессам с помощью моделирования по методу Монте-Карло.
Разметка данных в анализе временных рядов (Часть 4): Декомпозиция интерпретируемости с использованием разметки данных Разметка данных в анализе временных рядов (Часть 4): Декомпозиция интерпретируемости с использованием разметки данных
В этой серии статей представлены несколько методов разметки временных рядов, которые могут создавать данные, соответствующие большинству моделей искусственного интеллекта (ИИ). Целевая разметка данных может сделать обученную модель ИИ более соответствующей пользовательским целям и задачам, повысить точность модели и даже помочь модели совершить качественный скачок!
Создаем простой мультивалютный советник с использованием MQL5 (Часть 5): Полосы Боллинджера на канале Кельтнера — Сигналы индикаторов Создаем простой мультивалютный советник с использованием MQL5 (Часть 5): Полосы Боллинджера на канале Кельтнера — Сигналы индикаторов
Под мультивалютным советником в этой статье понимается советник, или торговый робот, который может торговать (открывать/закрывать ордера, управлять ордерами, например, трейлинг-стоп-лоссом и трейлинг-профитом) более чем одной парой символов с одного графика. В этой статье мы будем использовать сигналы двух индикаторов - полосы Боллинджера (Bollinger Bands®) на канале Кельтнера.
Пишем первую модель стеклянного ящика (Glass Box) на Python и MQL5 Пишем первую модель стеклянного ящика (Glass Box) на Python и MQL5
Модели машинного обучения трудно интерпретировать, и понимание того, почему модели не совпадают с нашими ожиданиями, может очень сильно помочь в конечном итоге достичь нужного результата от использования таких современных методов. Без всестороннего понимания внутренней работы модели может быть сложно найти ошибки, которые ухудшают производительность. При этом можно тратить время на создание функций, которые не влияют на качество прогноза. В итоге, какой бы хорошей ни была модель, мы упускаем все ее основные преимущества из-за собственных ошибок. К счастью, существует сложное, но при этом хорошо разработанное решение, которое позволяет ясно увидеть, что происходит под капотом модели.