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

Mohamed Abdelmaaboud | 4 апреля, 2024

Введение

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

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

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

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

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

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

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

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

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


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

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

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

Что делает шаблон?

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

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

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

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

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

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

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

Как его использовать в 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). Шаблон помогает инкапсулировать запрос в объект, и это позволяет нам устанавливать параметры для разных запросов, не меняя отправителя или получателя, что означает, что теперь между отправителем, процессором и получателем применяется разделение. Это очень полезно, когда классы обладают обширным функционалом. Шаблон также поддерживает операции отмены.

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

Что делает шаблон?

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

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

КОМАНДА

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

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

Как его использовать в 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)

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

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

Что делает шаблон?

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

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

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

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

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

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

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

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

Как его использовать в 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).

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

Что делает шаблон?

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

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

ИТЕРАТОР

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

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

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

Как его использовать в 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)

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

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

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

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

Как его использовать в 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.

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

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

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

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