English Deutsch 日本語
preview
Шаблоны проектирования в программировании на MQL5 (Часть 4): Поведенческие шаблоны 2

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

MetaTrader 5Трейдинг | 15 апреля 2024, 14:04
433 3
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Введение

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

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

  • Напоминание (Memento) - восстанавливает объект в сохраненном состоянии путем захвата и экстернализации его внутреннего состояния без нарушения инкапсуляции.
  • Наблюдатель (Observer) - определяет зависимость "один ко многим" между объектами - когда один объект меняет свое состояние, все его зависимости получают уведомление и автоматически обновляются.
  • Состояние (State) - меняет поведение объекта при изменении его внутреннего состояния. Кажется, что объект меняет свой класс.
  • Стратегия (Strategy) - идентифицирует семейство алгоритмов, обеспечивает их инкапсуляцию и взаимозаменяемость. Стратегия позволяет алгоритму меняться независимо от клиентов, которые его используют.
  • Метод шаблона (Template Method) - идентифицирует основы алгоритма в операции, оставляя некоторые шаги подклассам, позволяя подклассам повторно идентифицировать их без изменения структуры алгоритмов.
  • Посетитель (Visitor) - идентифицировать новую операцию, не оказывая никакого влияния на классы элементов, над которыми выполняется операция.
  • Заключение

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

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

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

Тема неразрывно связана с объектно-ориентированным программированием, о котором мы говорили в статье "Объектно-ориентированное программирование (ООП). Я надеюсь, что мои статьи позволят вам усовершенствовать навыки программирования путем определения одной из наиболее важных тем — шаблонов проектирования для написания чистого, расширяемого, многократно используемого и хорошо протестированного кода.

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

Напоминание (Memento)

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

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

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

Напоминание (Memento)

На схеме видно, что в данном шаблоне участвуют следующие элементы:

  • Напоминание (Memento) - сохраняет состояние по мере необходимости по усмотрению Инициатора (Originator). Не предоставляет доступа к объектам, кроме Инициатора. Может хранить столько информации о внутреннем состоянии Инициатора, сколько необходимо по усмотрению Инициатора. У Напоминания есть два интерфейса: узкий или широкий, в зависимости от точки зрения Смотрителя (Caretaker) или Инициатора.
  • Инициатор (Originator) - создает напоминание, содержащее текущий снимок внутреннего состояния, и восстанавливает внутреннее состояние.
  • Смотритель (Caretaker) - сохраняет напоминание без проверки его содержимого.

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

  • Большая ресурсоемкость при большой копии.
  • Потеря исторических данных, так как количество слотов для снимков ограничено.
  • Не раскрывает никакой информации, кроме напоминания.

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

  • Он сохраняет границы инкапсуляции.
  • Определяет узкие и широкие интерфейсы.
  • Его можно использовать для упрощения инициатора.

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

Попробуем использовать Напоминание в MQL5 следующим образом:

Объявим класс Memento, используя ключевое слово class.

class Memento
  {
protected:
   string            m_state;
public:
   string            GetState(void);
   void              SetState(string);
                     Memento(string);
  };
Memento::Memento(string state):
   m_state(state)
  {
  }
string Memento::GetState(void)
  {
   return m_state;
  }
void Memento::SetState(string state)
  {
   m_state=state;
  }

Объявим класс Originator

class Originator
  {
protected:
   string            m_state;
public:
   void              SetMemento(Memento& memento);
   Memento*          CreateMemento(void);
   string            State(void);
   void              State(string);
  };
void Originator::SetMemento(Memento& memento)
  {
   m_state=memento.GetState();
  }
Memento* Originator::CreateMemento(void)
  {
   return new Memento(m_state);
  }
void Originator::State(string state)
  {
   m_state=state;
  }
string Originator::State(void)
  {
   return m_state;
  }

Объявим класс Caretaker

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

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

//+------------------------------------------------------------------+
//|                                                      Memento.mqh |
//+------------------------------------------------------------------+
class Memento
  {
protected:
   string            m_state;
public:
   string            GetState(void);
   void              SetState(string);
                     Memento(string);
  };
Memento::Memento(string state):
   m_state(state)
  {
  }
string Memento::GetState(void)
  {
   return m_state;
  }
void Memento::SetState(string state)
  {
   m_state=state;
  }
class Originator
  {
protected:
   string            m_state;
public:
   void              SetMemento(Memento& memento);
   Memento*          CreateMemento(void);
   string            State(void);
   void              State(string);
  };
void Originator::SetMemento(Memento& memento)
  {
   m_state=memento.GetState();
  }
Memento* Originator::CreateMemento(void)
  {
   return new Memento(m_state);
  }
void Originator::State(string state)
  {
   m_state=state;
  }
string Originator::State(void)
  {
   return m_state;
  }
class Caretaker
  {
public:
   Memento*          memento;
                    ~Caretaker(void);
  };
Caretaker::~Caretaker(void)
  {
   if(CheckPointer(memento)==1)
     {
      delete memento;
     }
  }

Наблюдатель (Observer)

Наблюдатель определяет зависимость "один ко многим" между объектами - когда один объект меняет свое состояние, все его зависимости получают уведомление и автоматически обновляются. Шаблон также известен как Dependents (зависимости) и Publish-Subscribe (публикация-подписка).

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

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

Учитывая всё сказанное, шаблон можно графически представить так:

Наблюдатель (Observer)

Элементы шаблона:

  • Субъект (Subject) - идентифицирует наблюдателей и их количество. Имеется интерфейс для прикрепления и отсоединения объектов наблюдателя.
  • Наблюдатель (Observer) - идентифицирует обновленный интерфейс для объектов, которые необходимо уведомлять об изменениях в субъекте.
  • Определенный субъект (ConcreteSubject) - сохраняет состояние в объектах Определенного наблюдателя (ConcreteObserver). Когда происходит изменение состояния, он отправляет уведомление об этом наблюдателям.
  • Определенный наблюдатель (ConcreteObserver) - способствует достижению следующих трех целей:
    • Сохранение ссылки на объект Определенного субъекта.
    • Сохранение состояния.
    • Выполнение интерфейса обновления наблюдателя.

При использовании шаблона возможны следующие подводные камни:

  • Наблюдаемый не может определить, какой объект обновил свое состояние.
  • Большие обновления.
  • Сложность отладки.

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

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

  • Обеспечить абстрактную связь между субъектом и наблюдателем.
  • Обеспечить поддержку взаимодействия.

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

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

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

Используем ключевое слово class для объявления Субъекта

class Subject
  {
public:
                     Subject(void);
                    ~Subject(void);
   void              Attach(Observer* observer);
   void              Detach(Observer& observer);
   void              Notify(void);
   void              State(string state);
   string            State(void) {return m_state;}

protected:
   string            m_state;
   Observer*         m_observers[];

   int               Find(Observer& observer);
  };
Subject::Subject(void):
   m_state(NULL)
  {
  }
Subject::~Subject(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(CheckPointer(item)==1)
        {
         delete item;
        }
     }
  }
void Subject::State(string state)
  {
   m_state=state;
  }
void Subject::Notify(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      m_observers[i].Update(m_state);
     }
  }
void Subject::Attach(Observer *observer)
  {
   int size=ArraySize(m_observers);
   ArrayResize(m_observers,size+1);
   m_observers[size]=observer;
  }
void Subject::Detach(Observer &observer)
  {
   int find=Find(observer);
   if(find==-1)
      return;
   Observer* item=m_observers[find];
   if(CheckPointer(item)==1)
      delete item;
   ArrayRemove(m_observers,find,1);
  }
int Subject::Find(Observer &observer)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(item==&observer)
         return i;
     }
   return -1;
  }

Использование ключевого слова class для объявления Определенного субъекта

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

Использование ключевого слова class для объявления Определенного наблюдателя

class ConcreteObserver:public Observer
  {
public:
   void              Update(string state);
                     ConcreteObserver(ConcreteSubject& subject);
protected:
   string            m_observer_state;
   ConcreteSubject*  m_subject;
  };
ConcreteObserver::ConcreteObserver(ConcreteSubject& subject):
   m_subject(&subject)
  {
  }
void ConcreteObserver::Update(string state)
  {
   m_observer_state=state;
  }

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

//+------------------------------------------------------------------+
//|                                                     Observer.mqh |
//+------------------------------------------------------------------+
interface Observer
  {
   void Update(string state);
  };
class Subject
  {
public:
                     Subject(void);
                    ~Subject(void);
   void              Attach(Observer* observer);
   void              Detach(Observer& observer);
   void              Notify(void);
   void              State(string state);
   string            State(void) {return m_state;}

protected:
   string            m_state;
   Observer*         m_observers[];

   int               Find(Observer& observer);
  };
Subject::Subject(void):
   m_state(NULL)
  {
  }
Subject::~Subject(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(CheckPointer(item)==1)
        {
         delete item;
        }
     }
  }
void Subject::State(string state)
  {
   m_state=state;
  }
void Subject::Notify(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      m_observers[i].Update(m_state);
     }
  }
void Subject::Attach(Observer *observer)
  {
   int size=ArraySize(m_observers);
   ArrayResize(m_observers,size+1);
   m_observers[size]=observer;
  }
void Subject::Detach(Observer &observer)
  {
   int find=Find(observer);
   if(find==-1)
      return;
   Observer* item=m_observers[find];
   if(CheckPointer(item)==1)
      delete item;
   ArrayRemove(m_observers,find,1);
  }
int Subject::Find(Observer &observer)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(item==&observer)
         return i;
     }
   return -1;
  }
class ConcreteSubject:public Subject
  {
  public:
   void              State(string state);
   string            State(void) {return m_state;}
  };
void ConcreteSubject::State(string state)
  {
   m_state=state;
  }  
class ConcreteObserver:public Observer
  {
public:
   void              Update(string state);
                     ConcreteObserver(ConcreteSubject& subject);
protected:
   string            m_observer_state;
   ConcreteSubject*  m_subject;
  };
ConcreteObserver::ConcreteObserver(ConcreteSubject& subject):
   m_subject(&subject)
  {
  }
void ConcreteObserver::Update(string state)
  {
   m_observer_state=state;
  }

Состояние (State)

Шаблон позволяет объекту изменять свое поведение в случае изменения его внутреннего состояния. Объект будет изменять свой класс. Он также известен как Objects for States (объекты для состояний). Мы можем использовать его, когда поведение объекта зависит от его состояния, изменяя поведение объекта в зависимости от состояния во время выполнения. Его также можно использовать, когда у нас есть операции с большими условными операторами, при этом всё зависит от состояния объекта.

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

Ниже показана работа шаблона:

Состояние (State)

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

Контекст (context) - идентифицирует интерфейс, необходимый клиенту. Он также поддерживает подкласс экземпляра определенного состояния (ConcreteState), который идентифицирует текущее состояние.

Состояние (State) - идентифицирует интерфейс для инкапсуляции поведения определенного состояния контекста.

Подклассы определенного состояния (ConcreteState subclasses) - подкласс выполняет поведение состояния контекста. Это касается каждого подкласса.

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

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

Однако есть и преимущества:

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

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

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

Объявим класс Context

class Context;

Объявим интерфейс шаблона

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

Объявим объект m_state

State*            m_state;

Объявим класс Context

class Context
  {
public:
                     Context(State& state);
                    ~Context(void);
   State*            State(void) {return m_state;}
   void              State(State& state);
   void              Request(void);
  };
Context::~Context(void)
  {
   if(CheckPointer(m_state)==1)
      delete m_state;
  }
void Context::State(State& state)
  {
   delete m_state;
   m_state=&state;
  }
void Context::Request(void)
  {
   m_state.Handle(this);
  }

Объявим класс ConcreteStateA

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

Объявим класс ConcreteStateB

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

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

//+------------------------------------------------------------------+
//|                                                        State.mqh |
//+------------------------------------------------------------------+
class Context;
interface State
  {
   void Handle(Context& context);
  };
State*            m_state;
class Context
  {
public:
                     Context(State& state);
                    ~Context(void);
   State*            State(void) {return m_state;}
   void              State(State& state);
   void              Request(void);
  };
Context::~Context(void)
  {
   if(CheckPointer(m_state)==1)
      delete m_state;
  }
void Context::State(State& state)
  {
   delete m_state;
   m_state=&state;
  }
void Context::Request(void)
  {
   m_state.Handle(this);
  }
  
class ConcreteStateA:public State
  {
public:
   void              Handle(Context& context);
  };
void ConcreteStateA::Handle(Context& context)
  {
   context.State(new ConcreteStateB);
  }

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

Стратегия (Strategy)

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

Итак, можем использовать шаблон, когда:

  • Многие родственные классы отличаются только своим поведением. Стратегии предоставляют метод для настройки поведения класса одним из многих способов.
  • Нам нужно использовать разные варианты одного и того же алгоритма. А также нам нужна возможность выбирать алгоритм во время выполнения.
  • Алгоритм содержит данных, к которым у клиентов не должно быть доступа.
  • Класс определяет множество вариантов поведения, которые в его операциях проявляются как несколько условных операторов. Вместо множества связанных условных ветвей он может находиться в отдельном классе стратегии.
  • Необходимо удалить условные операторы.

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

Ниже показана работа шаблона:

Стратегия (Strategy)

Элементы шаблона:

  • Стратегия (Strategy) - объявляет интерфейс, общий для всех поддерживаемых алгоритмов, который будет использоваться контекстом для вызова алгоритма, установленного определенной стратегией (ConcreteStrategy).
  • Определенная стратегия (ConcreteStrategy) - контекст реализует алгоритм, используя интерфейс шаблона.
  • Контекст (Context) - создается объектом Определенной стратегии, поддерживает ссылку на объект стратегии и может идентифицировать интерфейс, чтобы предоставить стратегии доступ к своим данным.

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

  • Клиент должен знать о стратегиях.
  • Увеличенное количество классов.

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

Последствия (преимущества) использования шаблона:

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

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

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

interface Strategy
  {
   void AlgorithmInterface(void);
  };

Объявим класс Context

class Context
  {
public:
                     Context(Strategy& strategy);
                    ~Context(void);

   void              ContextInterface(void);
protected:
   Strategy*         m_strategy;
  };
Context::Context(Strategy& strategy)
  {
   m_strategy=&strategy;
  }
Context::~Context(void)
  {
   if(CheckPointer(m_strategy)==1)
      delete m_strategy;
  }
void Context::ContextInterface(void)
  {
   m_strategy.AlgorithmInterface();
  }

Объявим класс ConcreteStrategyA

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

Объявим класс ConcreteStrategyB

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

Объявим класс ConcreteStrategyC

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

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

//+------------------------------------------------------------------+
//|                                                     Strategy.mqh |
//+------------------------------------------------------------------+
interface Strategy
  {
   void AlgorithmInterface(void);
  };
class Context
  {
public:
                     Context(Strategy& strategy);
                    ~Context(void);

   void              ContextInterface(void);
protected:
   Strategy*         m_strategy;
  };
Context::Context(Strategy& strategy)
  {
   m_strategy=&strategy;
  }
Context::~Context(void)
  {
   if(CheckPointer(m_strategy)==1)
      delete m_strategy;
  }
void Context::ContextInterface(void)
  {
   m_strategy.AlgorithmInterface();
  }
class ConcreteStrategyA : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyA::AlgorithmInterface(void)
  {
  }
class ConcreteStrategyB : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyB::AlgorithmInterface(void)
  {
  }
class ConcreteStrategyC : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyC::AlgorithmInterface(void)
  {
  }  

Метод шаблона (Template Method)

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

Итак, мы можем использовать шаблон, когда:

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

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

Ниже показана работа шаблона:

 Метод шаблона (Template Method)

Элементы шаблона:

  • Абстрактный класс (AbstractClass) - используется для указания абстрактных примитивных операций, которые должны быть определены конкретными подклассами для реализации различных шагов алгоритма. Его также можно использовать для выполнения метода шаблона, описывающего структуру алгоритма. Метод вызывает как примитивные операции, так и операции, указанные в Абстрактном классе или принадлежащие другим объектам.
  • Определенный класс (ConcreteClass) - используется для выполнения примитивных операций, необходимых для выполнения шагов алгоритма, специфичного для подкласса.

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

Преимущества и подводные камни:

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

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

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

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

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

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

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

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

//+------------------------------------------------------------------+
//|                                              Template_Method.mqh |
//+------------------------------------------------------------------+
class AbstractClass
  {
public:
   virtual void      PrimitiveOperation1(void)=0;
   virtual void      PrimitiveOperation2(void)=0;
   virtual void      TemplateMethod(void);
  };
void AbstractClass::TemplateMethod(void)
  {
   PrimitiveOperation1();
   PrimitiveOperation2();
  }
  class ConcreteClass : public AbstractClass
  {
public:
   void              PrimitiveOperation1(void);
   void              PrimitiveOperation2(void);
  };
void ConcreteClass::PrimitiveOperation1(void)
  {
  }
void ConcreteClass::PrimitiveOperation2(void)
  {
  }

Посетитель (Visitor)

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

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

Схема ниже показывает принцип работы шаблона:

Посетитель (Visitor)

Элементы шаблона:

  • Посетитель (Visitor) - для каждого класса ConcreteElement в структуре объекта объявляется операция Visit (посещение). Имя и подпись операции однозначно идентифицируют класс, отправляющий запрос на посещение посетителя. Такая конструкция обеспечивает прямой доступ к элементу через его специальный интерфейс, чтобы посетитель мог определить конкретный класс посещаемого элемента.
  • Определенный посетитель (ConcreteVisitor) - реализует каждую операцию, заявленную посетителем. Каждая операция реализует фрагмент алгоритма, определенный для соответствующего класса объекта в структуре. Определенный посетитель предоставляет контекст для алгоритма и сохраняет его локальное состояние. Это состояние часто представляет собой накопление результатов во время итерации структуры.
  • Элемент (Element) - определяет принимающую операцию, которая принимает посетителя в качестве аргумента.
  • Определенный элемент (ConcreteElement) - выполняет операцию принятия, которая принимает посетителя в качестве аргумента.
  • Структура объекта (ObjectStructure) - может перечислять свои элементы, предоставлять высокоуровневый интерфейс, позволяющий посетителю посещать элементы. Структура может быть либо составной, либо представлять собой коллекцию.

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

В соответствии с тем, что мы упомянули о шаблоне он имеет следующие преимущества:

  • Позволяет легко добавлять новые операции.
  • Позволяет различать связанные и несвязанные операции, поскольку объединяет связанные и отдельные несвязанные операции.

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

Объявить интерфейс шаблона

interface Visitor;

Объявите класс Element

class Element
  {
protected:
   Visitor*          m_visitor;
public:
                    ~Element(void);
   virtual void      Accept(Visitor* visitor)=0;
protected:
   void              Switch(Visitor* visitor);
  };
Element::~Element(void)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
  }
void Element::Switch(Visitor *visitor)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
   m_visitor=visitor;
  }

Объявим класс ConcreteElementA

class ConcreteElementA : public Element
  {
public:
   void              Accept(Visitor*);
   void              OperationA(void);
  };
void ConcreteElementA::OperationA(void)
  {
  }
void ConcreteElementA::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementA(&this);
  }

Объявим класс ConcreteElementB

class ConcreteElementB : public Element
  {
public:
   void              Accept(Visitor* visitor);
   void              OperationB(void);
  };
void ConcreteElementB::OperationB(void)
  {
  }
void ConcreteElementB::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementB(&this);
  }

Используем ключевое слово interface для определения шаблоном VisitElementA и VisitElementB внутри

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

Объявим класс ConcreteVisitor1

class ConcreteVisitor1 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA* visitor);
   void              VisitElementB(ConcreteElementB* visitor);
  };
void ConcreteVisitor1::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor1::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }

Объявим класс ConcreteVisitor2

class ConcreteVisitor2 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA*);
   void              VisitElementB(ConcreteElementB*);
  };
void ConcreteVisitor2::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor2::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }

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

//+------------------------------------------------------------------+
//|                                                      Visitor.mqh |
//+------------------------------------------------------------------+
interface Visitor;
class Element
  {
protected:
   Visitor*          m_visitor;
public:
                    ~Element(void);
   virtual void      Accept(Visitor* visitor)=0;
protected:
   void              Switch(Visitor* visitor);
  };
Element::~Element(void)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
  }
void Element::Switch(Visitor *visitor)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
   m_visitor=visitor;
  }
class ConcreteElementA : public Element
  {
public:
   void              Accept(Visitor*);
   void              OperationA(void);
  };
void ConcreteElementA::OperationA(void)
  {
  }
void ConcreteElementA::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementA(&this);
  }
class ConcreteElementB : public Element
  {
public:
   void              Accept(Visitor* visitor);
   void              OperationB(void);
  };
void ConcreteElementB::OperationB(void)
  {
  }
void ConcreteElementB::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementB(&this);
  }
interface Visitor
  {
   void VisitElementA(ConcreteElementA*);
   void VisitElementB(ConcreteElementB*);
  };
class ConcreteVisitor1 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA* visitor);
   void              VisitElementB(ConcreteElementB* visitor);
  };
void ConcreteVisitor1::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor1::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }
class ConcreteVisitor2 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA*);
   void              VisitElementB(ConcreteElementB*);
  };
void ConcreteVisitor2::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor2::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }

Заключение

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

Мы рассмотрели следующие шаблоны:

  • Порождающие
    • Абстрактная фабрика
    • Строитель
    • Фабричный метод
    • Прототип
    • Одиночка
  • Структурные
    • Адаптер
    • Мост
    • Компоновщик
    • Декоратор
    • Фасад
    • Приспособленец
    • Заместитель
  • Поведенческие шаблоны
    • Цепочка ответственности
    • Команда
    • Интерпретатор
    • Итератор
    • Медиатор
    • Напоминание
    • Наблюдатель
    • Состояние
    • Метод шаблона
    • Посетитель

Остальные статьи из этой серии вы можете прочитать, перейдя по ссылкам ниже:

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

  • 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

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

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

Прикрепленные файлы |
Memento.mqh (1.34 KB)
Observer.mqh (2.5 KB)
State.mqh (1.19 KB)
Strategy.mqh (1.24 KB)
Visitor.mqh (2.14 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
DidMa
DidMa | 23 дек. 2023 в 05:03

Спасибо за эту замечательную статью.

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

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

Я упрощу процесс:
Сам эксперт является объектом и становится Контекстом:

  • У меня есть Enum, чтобы иметь возможность выбирать стратегию (как пример STRATEGY_RSI, STRATEGY_MA,...)
  • У меня есть оператор switch в OnInit(), на перечислении стратегий, и в зависимости от результата, я

switch(strategyName)
        {
         case STRATEGY_RSI :
            m_strategy = new Strategy_RSI();
            break;
         case STRATEGY_MA :
            m_strategy = new Strategy_MA();
            break;
         default:
            break;
}

  • Я устанавливаю входные параметры в стратегию через объект параметров (например, mqlparams).
[...]
// add the signal parameters to the strategy through the expert Method
Expert.AddParameterObject(signal_RSI_Params); // signal_RSI_Params is the parameter object that has the input parameters inside

//detail of the AddParameterObject method (simplified):
bool CExpert::AddParameterObject(CParameter & parameter)
  {
         if(!m_strategy.AddParameterObject(parameter))
           {
            return(false);
           }
     }
   return true;
  }

// add the generic expert parameters.
Expert.InitStrategyParameters(expert_1_Params);

//detail of the method  (simplified) :
bool CExpert::InitStrategyParameters(CParameter & expertParameters)
  {
// 
   if(!m_strategy.InitStrategyParameters(expertParameters))
     {
     [...]
      return false;
     }
   return true;
  }


  • Все стратегии реализуют одни и те же методы (открыть сигнал, закрыть сделку, ...)
  • Все стратегии могут загружать сигналы или свои пользовательские индикаторы

в функции OnTick эксперта я вызываю общие методы стратегии.

bool CExpert::CheckOpenLong(void)
  {
    [...]
    if(true == m_strategy.CheckOpenLong([...]))
     {
      [...]
     }
    [...]
}


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

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


Я хотел бы получить ваши отзывы о том, как я это реализовал, является ли это шаблоном стратегии, или гибридом, что можно улучшить?

И есть ли у вас идея, как мы могли бы иметь (я не думаю, что это возможно в MT5) динамические контекстные входные параметры?


Большое спасибо

Дидма

Altan Karakaya
Altan Karakaya | 24 дек. 2023 в 08:46

очень поучительно

Yuriy Yepifanov
Yuriy Yepifanov | 16 апр. 2024 в 12:06

а почему одинаковые по функционалу функции имеют разные названия
например тут


void Memento::SetState(string state)
  {
   m_state=state;
  }
  
  
void Originator::State(string state)
  {
   m_state=state;
  }


 

Алгоритм оптимизации на основе мозгового штурма — Brain Storm Optimization (Часть I): Кластеризация Алгоритм оптимизации на основе мозгового штурма — Brain Storm Optimization (Часть I): Кластеризация
В данной статье мы рассмотрим инновационный метод оптимизации, названный BSO (Brain Storm Optimization), который вдохновлен природным явлением - "мозговым штурмом". Мы также обсудим новый подход к решению многомодальных задач оптимизации, который использует метод BSO и позволяет находить несколько оптимальных решений без необходимости заранее определять количество подпопуляций. В статье мы также рассмотрим методы кластеризации K-Means и K-Means++.
Машинное обучение и Data Science (Часть 17): Растут ли деньги на деревьях? Случайные леса в форекс-трейдинге Машинное обучение и Data Science (Часть 17): Растут ли деньги на деревьях? Случайные леса в форекс-трейдинге
Эта статья познакомит вас с секретами алгоритмической алхимии, познакомит с искусством и точностью особенностей финансовых ландшафтов. Вы узнаете, как случайные леса преобразуют данные в прогнозы и помогают ориентироваться в сложностях финансовых рынков. Мы постараемся определить роль случайных лесов в отношении финансовых данных и проверить, смогут ли они помочь увеличить прибыль.
Разработка системы репликации (Часть 33): Система ордеров (II) Разработка системы репликации (Часть 33): Система ордеров (II)
Сегодня мы продолжим разработку системы ордеров, но вы увидите, что мы будем массово использовать заново то, что уже было показано в других статьях. Тем не менее, в этой статье мы получим небольшое вознаграждение. Сначала мы разработаем систему, которую можно будет использовать вместе с реальным торговым сервером, либо с помощью демо-счета, либо реального счета. Мы будем широко использовать платформу MetaTrader 5, которая обеспечит нам всю необходимую поддержку в начале данного пути.
Нейросети — это просто (Часть 85): Многомерное прогнозирование временных рядов Нейросети — это просто (Часть 85): Многомерное прогнозирование временных рядов
В данной статье хочу познакомить Вас с новым комплексным методом прогнозирования временных рядов, который гармонично сочетает в себе преимущества линейных моделей и трансформеров.