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

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

Введение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Как его использовать в 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 для написания чистого поддерживаемого, многоразового и хорошо протестированного кода.

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

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

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

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