Шаблоны проектирования в программировании на MQL5 (Часть 3): Поведенческие шаблоны 1
Введение
Статья продолжает серию о шаблонах проектирования в области программного обеспечения. В двух предыдущих статьях этой серии мы определили два типа таких шаблонов - порождающие и структурные. В этой статье мы дадим определение поведенческим шаблонам, а также рассмотрим, как они могут быть полезны при создании, сборке или разработке нашего программного обеспечения и как мы можем использовать их в MQL5 для создания нашего программного обеспечения для MetaTrader 5, чтобы создать надежное, поддерживаемое, многоразовое, хорошо протестированное и расширяемое программное обеспечение.
Основные понятия, которые будут использованы в статье:
- Поведенческие шаблоны
- Цепочка ответственности (Chain of responsibility)
- Команда (command)
- Интерпретатор (Interpreter)
- Итератор (Iterator)
- Медиатор (Mediator)
- Заключение
Я рекомендую прочитать также и статьи о порождающих и структурных шаблонах, чтобы получить общее представление об одной из наиболее важных тем в разработке программного обеспечения - шаблонах проектирования, поскольку они могут быть очень полезны в разработке приложений.
Поведенческие шаблоны
В этой статье мы рассмотрим последний тип шаблонов проектирования - поведенческие шаблоны. Мы уже знаем, что порождающие шаблоны помогают создавать независимое программное обеспечение или систему путем создания, составления и представления объектов, а структурные шаблоны можно использовать для создания более крупных структур с использованием созданных объектов и классов.
В этой статье мы рассмотрим поведенческие шаблоны, связанные с назначением и распределением обязанностей между объектами. Они также определяют, как объекты могут взаимодействовать друг с другом, и существует множество шаблонов этого типа, например следующие:
- Цепочка ответственности (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)
Еще один шаблон поведенческого проектирования, который можно использовать для определения того, как объекты могут взаимодействовать друг с другом. Медиатор можно использовать, когда у нас есть набор объектов и нам нужно определить инкапсулированный объект, чтобы описать, как этот набор объектов может взаимодействовать. Он также применяет разделение, что может быть полезно и позволяет нам независимо изменять взаимодействие объектов.
Возможные подводные камни:
- Один медиатор для всего.
- Используется с другими шаблонами.
Шаблон помогает установить метод взаимодействия между объектами без явного упоминания каждого объекта. Это позволяет применить разделение между объектами. Его также можно использовать в качестве маршрутизатора и для управления взаимодействием.
Схема шаблона:
Как видим, в состав шаблона входят:
- 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
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
после
дальше можно не читать
после
дальше можно не читать
Это перевод с английского оригинала.
Можете мысленно заменить на "обработчик". Ну или просто не читать, а писать свои статьи.
Статья по теме https://habr.com/ru/articles/113995/
Это перевод с английского оригинала.
Можете мысленно заменить на "обработчик". Ну или просто не читать, а писать свои статьи.
Статья по теме https://habr.com/ru/articles/113995/
не о переводе речь.. в статье текста практически и нет, поэтому к коду предвзятое строгое отношение
код со скриншота имеет у вас шанс пройти code-review ?
а поводу "писать свои" - вы в курсе дел, я предлагал серию про использование gcc и окружение msys2, но оказалось что кроме MSVC нельзя
А как по Вашему "правильно"?
Это вообще для чего? Посмотрел материал по итераторам, есть такие варианты:
1)
2)