English Español Deutsch 日本語
preview
Шаблоны проектирования в MQL5 (Часть 2): Структурные шаблоны

Шаблоны проектирования в MQL5 (Часть 2): Структурные шаблоны

MetaTrader 5Трейдинг | 21 марта 2024, 14:25
486 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Введение

Представляю вашему вниманию новую статью, посвященную важному аспекту разработки программного обеспечения — шаблонам проектирования. В предыдущей статье мы говорили о порождающих шаблонах. Если вы ее не читали, рекомендую начать именно с нее: Шаблоны проектирования в MQL5 (Часть I): Порождающие шаблоны. Предыдущая статья представляет собой введение в тему дизайн-паттернов и рассказывает в целом, насколько они полезны при разработке программного обеспечения.

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

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

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

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

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


Структурные шаблоны

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

Типы структурных шаблонов:

  • Адаптер (Adapter) — позволяет получить интерфейс, который ожидает клиентами, путем преобразования интерфейса класса.
  • Мост (Bridge) — позволяет разделять абстракцию и ее реализацию, чтобы они могли изменяться независимо.
  • Компоновщик (Composite) — объединяет объекты в древовидную структуру, чтобы представить иерархию от частного к целому. Кроме того, компоновщик позволяет клиентам одинаково обрабатывать отдельные объекты и их композиции.
  • Декоратор (Decorator) — его можно использовать для динамического подключения дополнительного поведения к текущим объектам, а также в качестве гибкой альтернативы практике создания подклассов с целью расширения функциональности.
  • Фасад (Facade) — позволяет получить унифицированный интерфейс для набора интерфейсов в подсистеме через определение одной точки взаимодействия с подсистемой.
  • Приспособленец (Flyweight) — используется для уменьшения затрат при работе с большим количеством мелких объектов.
  • Заместитель (Proxy) — предоставляет объект, который контролирует доступ к другому объекту, перехватывая все вызовы. 

При рассмотрении этих паттернов, постараемся по каждому ответить на следующие вопросы:

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


Адаптер (Adapter)

Разбираться с типами шаблонов структурного проектирования с самого первого — адаптера. Ключевое слово для понимания этой модели — адаптивность. Проще говоря, если у нас есть интерфейс, который можно использовать в определенных условиях, а затем в этих условиях происходят обновления, нужно вносить обновления в интерфейс, чтобы код мог адаптироваться и эффективно работать в этих новых условиях. Именно это и делает шаблон — он преобразует интерфейс нашего класса в другой, который клиент может использовать. Таким образом, шаблон адаптера позволяет классам работать вместе в случаях несовместимых интерфейсов. Паттерн также называют оболочкой (Wrapper), потому что он создает класс-оболочку с требуемым интерфейсом.

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

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

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

Adapter1

Adapter1

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

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

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

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

Теперь посмотрим, как можно использовать этот шаблон (AdapterClass и ObjectClass) в MQL5. Итак, давайте по шагам разберем код:

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

namespace AdapterClass

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

interface Target
  {
   void Request();
  };

Используем функцию class для определения интерфейса класса Adaptee, который определяет существующий интерфейс, который нужно адаптировать с помощью одного открытого члена (SpecificRequest()).

class Adaptee
  {
public:
   void              SpecificRequest();
  };

 Вывод сообщения при выполнении запроса интерфейсом класса Adaptee

void Adaptee::SpecificRequest(void)
  {
   Print("A specific request is executing by the Adaptee");
  }

Объявляем класс Adapter, который адаптирует интерфейс адаптируемого объекта Adaptee к интерфейсам цели Target, которые наследуются от цели и адаптируемого объекта в виде множественного наследования.

class Adapter;
class AdapterAsTarget:public Target
  {
public:
   Adapter*          asAdaptee;
   void              Request();
  };
void AdapterAsTarget::Request()
  {
   printf("The Adapter requested Operation");
   asAdaptee.SpecificRequest();
  }
class Adapter:public Adaptee
  {
public:
   AdapterAsTarget*  asTarget;
                     Adapter();
                    ~Adapter();
  };
void Adapter::Adapter(void)
  {
   asTarget=new AdapterAsTarget;
   asTarget.asAdaptee=&this;
  }
void Adapter::~Adapter(void)
  {
   delete asTarget;
  }

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

class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output()
  {
   return __FUNCTION__;
  }

Запуск клиента

void Client::Run()
  {
   Adapter adapter;
   Target* target=adapter.asTarget;
   target.Request();
  }

Ниже приведен полный MQL5-код класса Adaptor одним блоком.

namespace AdapterClass
{
interface Target
  {
   void Request();
  };
class Adaptee
  {
public:
   void              SpecificRequest();
  };
void Adaptee::SpecificRequest(void)
  {
   Print("A specific request is executing by the Adaptee");
  }
class Adapter;
class AdapterAsTarget:public Target
  {
public:
   Adapter*          asAdaptee;
   void              Request();
  };
void AdapterAsTarget::Request()
  {
   printf("The Adapter requested Operation");
   asAdaptee.SpecificRequest();
  }
class Adapter:public Adaptee
  {
public:
   AdapterAsTarget*  asTarget;
                     Adapter();
                    ~Adapter();
  };
void Adapter::Adapter(void)
  {
   asTarget=new AdapterAsTarget;
   asTarget.asAdaptee=&this;
  }
void Adapter::~Adapter(void)
  {
   delete asTarget;
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output()
  {
   return __FUNCTION__;
  }
void Client::Run()
  {
   Adapter adapter;
   Target* target=adapter.asTarget;
   target.Request();
  }
}

Ниже показано, как использовать адаптер объекта в MQL5:

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

namespace AdapterObject

Используем interface для определения Target (целевой объект интерфейса).

interface Target
  {
   void Request();
  };

Создаем класса Adaptee для определения существующего интерфейса, который нужно адаптировать.

class Adaptee
  {
public:
   void              SpecificRequest();
  };
void Adaptee::SpecificRequest(void)
  {
   Print("The specific Request");
  }
class Adapter:public Target
  {
public:
   void              Request();
protected:
   Adaptee           adaptee;
  };
void Adapter::Request(void)
  {
   Print("The request of Operation requested");
   adaptee.SpecificRequest();
  }

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

class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output()
  {
   return __FUNCTION__;
  }

Запуск клиента, когда клиенты инициируют операции на экземпляре адаптера

void Client::Run()
  {
   Target* target=new Adapter;
   target.Request();
   delete target;
  }

Полный код выглядит так:

namespace AdapterObject
{
interface Target
  {
   void Request();
  };
class Adaptee
  {
public:
   void              SpecificRequest();
  };
void Adaptee::SpecificRequest(void)
  {
   Print("The specific Request");
  }
class Adapter:public Target
  {
public:
   void              Request();
protected:
   Adaptee           adaptee;
  };
void Adapter::Request(void)
  {
   Print("The request of Operation requested");
   adaptee.SpecificRequest();
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output()
  {
   return __FUNCTION__;
  }
void Client::Run()
  {
   Target* target=new Adapter;
   target.Request();
   delete target;
  }
}


Мост (Bridge)

В этой части познакомимся с другим структурным шаблоном проектирования — Bridge (мост). Основная идея использования этого шаблона — отделить абстракцию от ее реализаций, чтобы избежать любых конфликтов в будущем, которые могут возникнуть в случае обновлений или изменений в одной из них. Также его называют Handle или Body.

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

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

Структура шаблона "Мост" (Bridge) показана на схеме ниже:

Мост (Bridge)

На схеме Моста, показанной выше, есть следующие элементы:

  • Abstraction — абстракция, определяет интерфейс абстракции и поддерживает ссылку на объект-реализатора.
  • RefinedAbstraction — расширяет интерфейс абстракции.
  • Implementor — реализация, идентифицирует интерфейс классов реализации.
  • ConcreteImplementor — реализует интерфейс разработчика и идентифицирует конкретную реализацию этого интерфейса.

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

Шаблон моста можно использовать, когда нам нужно:

  • Уйти от постоянной связи между абстракцией и ее реализацией (разделить их с помощью шаблона).
  • Объединить различные абстракции и реализации и независимо расширять каждую из них без каких-либо конфликтов.
  • Избежать влияния на клиентов при изменении реализации абстракции.
  • Полностью скрыть реализацию абстракции от клиентов.

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

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

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

namespace Bridge

Далее создаем интерфейс Implementor — здесь используем ключевое слово interface, позволяющее определить функциональность, реализуемую классом.

interface Implementor
  {
   void OperationImp();
  };

Во фрагменте ниже мы создаем класс абстракции с открытыми и защищенными членами и сохраняем референс (ссылку) на объект разработчика.

class Abstraction
  {
public:
   virtual void      Operation();
                     Abstraction(Implementor*);
                     Abstraction();
                    ~Abstraction();
protected:
   Implementor*      implementor;
  };
void Abstraction::Abstraction(void) {}
void Abstraction::Abstraction(Implementor*i):implementor(i) {}
void Abstraction::~Abstraction()
  {
   delete implementor;
  }
void Abstraction::Operation()
  {
   implementor.OperationImp();
  }

Затем мы создаем класс RefinedAbstraction — он в данном примере будет участником

class RefinedAbstraction:public Abstraction
  {
public:
                     RefinedAbstraction(Implementor*);
   void              Operation();
  };
void RefinedAbstraction::RefinedAbstraction(Implementor*i):Abstraction(i) {}
void RefinedAbstraction::Operation()
  {
   Abstraction::Operation();
  }

Создаем классы ConcreteImplementorA и ConcreteImplementorB

class ConcreteImplementorA:public Implementor
  {
public:
   void              OperationImp();
  };
void ConcreteImplementorA::OperationImp(void)
  {
   Print("The implementor A");
  }
class ConcreteImplementorB:public Implementor
  {
public:
   void              OperationImp();
  };
void ConcreteImplementorB::OperationImp(void)
  {
   Print("The implementor B");
  }

Создаем класс клиента Client

class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }

Запуск клиента

void Client::Run(void)
  {
   Abstraction* abstraction;
   abstraction=new RefinedAbstraction(new ConcreteImplementorA);
   abstraction.Operation();
   delete abstraction;
   abstraction=new RefinedAbstraction(new ConcreteImplementorB);
   abstraction.Operation();
   delete abstraction;
  }

Ниже представлен код для структуры шаблона Мост (Bridge) целиком в одном блоке.

namespace Bridge
{
interface Implementor
  {
   void OperationImp();
  };
class Abstraction
  {
public:
   virtual void      Operation();
                     Abstraction(Implementor*);
                     Abstraction();
                    ~Abstraction();
protected:
   Implementor*      implementor;
  };
void Abstraction::Abstraction(void) {}
void Abstraction::Abstraction(Implementor*i):implementor(i) {}
void Abstraction::~Abstraction()
  {
   delete implementor;
  }
void Abstraction::Operation()
  {
   implementor.OperationImp();
  }
class RefinedAbstraction:public Abstraction
  {
public:
                     RefinedAbstraction(Implementor*);
   void              Operation();
  };
void RefinedAbstraction::RefinedAbstraction(Implementor*i):Abstraction(i) {}
void RefinedAbstraction::Operation()
  {
   Abstraction::Operation();
  }
class ConcreteImplementorA:public Implementor
  {
public:
   void              OperationImp();
  };
void ConcreteImplementorA::OperationImp(void)
  {
   Print("The implementor A");
  }
class ConcreteImplementorB:public Implementor
  {
public:
   void              OperationImp();
  };
void ConcreteImplementorB::OperationImp(void)
  {
   Print("The implementor B");
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Abstraction* abstraction;
   abstraction=new RefinedAbstraction(new ConcreteImplementorA);
   abstraction.Operation();
   delete abstraction;
   abstraction=new RefinedAbstraction(new ConcreteImplementorB);
   abstraction.Operation();
   delete abstraction;
  }
}


Компоновщик (Composite)

Компоновщик (Composite) — еще один тип структурного шаблона проектирования. Этот шаблон позволяет объединять объекты в дерево как структуру и тем самым обеспечивать единообразную обработку клиентами отдельных объектов и композиций.

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

Шаблон Компоновщик/Composite используется, когда нам нужно скомпоновать объекты в древовидные структуры. Поэтому дерево является основным элементом в этом шаблоне. При этом, если у нас есть компонент, который представлен в виде древовидной структуры, в этом компоненте по шаблону у нас есть два элемента: лист Leaf, который в данном случае выполняет только операции, и компонент самого компоновщика Composite, который выполняет набор операций, таких как добавление, удаление и вызов дочернего элемента.

Ниже приведена схема структуры шаблона проектирования "Компоновщик":

Компоновщик (Composite)

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

  • Component — компонент; он объявляет интерфейс объектов и реализует поведение интерфейса по умолчанию для классов, обеспечивает доступ и управление объявленными для этого компонентами интерфейса.
  • Leaf — лист; представляет объекты листа в композиции, и этот лист не имеет дочерних элементов, определяет поведение объектов, которые можно считать примитивными в композиции.
  • Composite — компоновщик идентифицирует поведение компонентов с дочерними элементами, сохраняет эти дочерние элементы компонентов и реализует операции дочерних элементов в интерфейсе компонентов.
  • Client — через интерфейс компонента клиент манипулирует объектами.

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

Данный шаблон Composite можно использовать, когда нам нужно:

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

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

Теперь посмотрим, как можно использовать шаблон компоновщика в MQL5. Итак, давайте по шагам разберем код:

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

namespace Composite

Создадим класс Component с открытыми и защищенными членами и доступом к родительскому элементу компонента.

class Component
  {
public:
   virtual void      Operation(void)=0;
   virtual void      Add(Component*)=0;
   virtual void      Remove(Component*)=0;
   virtual Component*   GetChild(int)=0;
                     Component(void);
                     Component(string);
protected:
   string            name;
  };
Component::Component(void) {}
Component::Component(string a_name):name(a_name) {}

Далее во фрагменте я решил показать ошибку пользователя при добавлении в лист и удалении из листа или при создании класса Leaf

#define ERR_INVALID_OPERATION_EXCEPTION   1
class Leaf:public Component
  {
public:
   void              Operation(void);
   void              Add(Component*);
   void              Remove(Component*);
   Component*        GetChild(int);
                     Leaf(string);
  };
void Leaf::Leaf(string a_name):Component(a_name) {}
void Leaf::Operation(void)
  {
   Print(name);
  }
void Leaf::Add(Component*)
  {
   SetUserError(ERR_INVALID_OPERATION_EXCEPTION);
  }
void Leaf::Remove(Component*)
  {
   SetUserError(ERR_INVALID_OPERATION_EXCEPTION);
  }
Component* Leaf::GetChild(int)
  {
   SetUserError(ERR_INVALID_OPERATION_EXCEPTION);
   return NULL;
  }

Создание класса Composite в качестве участника. Работа, добавление, удаление компонентов и GetChild(int)

class Composite:public Component
  {
public:
   void              Operation(void);
   void              Add(Component*);
   void              Remove(Component*);
   Component*        GetChild(int);
                     Composite(string);
                    ~Composite(void);
protected:
   Component*        nodes[];
  };
Composite::Composite(string a_name):Component(a_name) {}
Composite::~Composite(void)
  {
   int total=ArraySize(nodes);
   for(int i=0; i<total; i++)
     {
      Component* i_node=nodes[i];
      if(CheckPointer(i_node)==1)
        {
         delete i_node;
        }
     }
  }
void Composite::Operation(void)
  {
   Print(name);
   int total=ArraySize(nodes);
   for(int i=0; i<total; i++)
     {
      nodes[i].Operation();
     }
  }
void Composite::Add(Component *src)
  {
   int size=ArraySize(nodes);
   ArrayResize(nodes,size+1);
   nodes[size]=src;
  }
void Composite::Remove(Component *src)
  {
   int find=-1;
   int total=ArraySize(nodes);
   for(int i=0; i<total; i++)
     {
      if(nodes[i]==src)
        {
         find=i;
         break;
        }
     }
   if(find>-1)
     {
      ArrayRemove(nodes,find,1);
     }
  }
Component* Composite::GetChild(int i)
  {
   return nodes[i];
  }

Создание класса Client в качестве участника

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}

Запуск клиента

void Client::Run(void)
  {
   Component* root=new Composite("root");
   Component* branch1=new Composite("The branch 1");
   Component* branch2=new Composite("The branch 2");
   Component* leaf1=new Leaf("The leaf 1");
   Component* leaf2=new Leaf("The leaf 2");
   root.Add(branch1);
   root.Add(branch2);
   branch1.Add(leaf1);
   branch1.Add(leaf2);
   branch2.Add(leaf2);
   branch2.Add(new Leaf("The leaf 3"));
   Print("The tree");
   root.Operation();
   root.Remove(branch1); 
   Print("Removing one branch");
   root.Operation();
   delete root;
   delete branch1;
  }


Декоратор (Decorator)

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

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

Итак, шаблон позволяет добавлять обязанности к любому отдельному объекту, не делая этого во всем классе. Для этого используется обертка вместо использования метода создания подклассов.

Ниже приведена схема структуры шаблона проектирования "Декоратор":

Декоратор (Decorator)

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

  • Component (Компонент) — определяет интерфейс объектов и то, что они имеют дополнительные роли динамическим образом.
  • ConcreteComponent (конкретный компонент) — определяет, какой объект может налагать на него дополнительные обязанности.
  • Decorator (Декоратор) — позволяет сохранить ссылку на объект компонента и определить интерфейс, соответствующий интерфейсу компонента.
  • ConcreteDecorator (конкретный декоратор) — отвечает за добавление обязанностей к компоненту.

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

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

  • Динамически и прозрачно добавлять дополнительные обязанности к отдельным объектам и при этом не влиять на другие объекты.
  • Снимать обязанности с объектов.
  • Когда использование метода создания подклассов нецелесообразно для расширения.

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

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

Снова начинаем создания области объявления нашего Декоратора, внутри которого будем объявлять все, что нам нужно.

namespace Decorator

Создадим класс Component с открытым членом для определения интерфейса объектов.

class Component
  {
public:
   virtual void      Operation(void)=0;
  };

Создадим класс Decorator в качестве участника

class Decorator:public Component
  {
public:
   Component*        component;
   void              Operation(void);
  };
void Decorator::Operation(void)
  {
   if(CheckPointer(component)>0)
     {
      component.Operation();
     }
  }

Создадим класс ConcreteComponent в качестве участника

class ConcreteComponent:public Component
  {
public:
   void              Operation(void);
  };
void ConcreteComponent::Operation(void)
  {
   Print("The concrete operation");
  }

Создание ConcreteDecoratorA и ConcreteDecoratorB

class ConcreteDecoratorA:public Decorator
  {
protected:
   string            added_state;
public:
                     ConcreteDecoratorA(void);
   void              Operation(void);
  };
ConcreteDecoratorA::ConcreteDecoratorA(void):
   added_state("The added state()")
  {
  }
void ConcreteDecoratorA::Operation(void)
  {
   Decorator::Operation();
   Print(added_state);
  }
class ConcreteDecoratorB:public Decorator
  {
public:
   void              AddedBehavior(void);
   void              Operation(void);
  };
void ConcreteDecoratorB::AddedBehavior(void)
  {
   Print("The added behavior()");
  }
void ConcreteDecoratorB::Operation(void)
  {
   Decorator::Operation();
   AddedBehavior();
  }

Создаем класс клиента Client

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }

Запуск клиента

void Client::Run(void)
  {
   Component* component=new ConcreteComponent();
   Decorator* decorator_a=new ConcreteDecoratorA();
   Decorator* decorator_b=new ConcreteDecoratorB();
   decorator_a.component=component;
   decorator_b.component=decorator_a;
   decorator_b.Operation();
   delete component;
   delete decorator_a;
   delete decorator_b;
  }

Ниже я собрал весь код шаблона целиком в одном блоке

namespace Decorator
{
class Component
  {
public:
   virtual void      Operation(void)=0;
  };
class Decorator:public Component
  {
public:
   Component*        component;
   void              Operation(void);
  };
void Decorator::Operation(void)
  {
   if(CheckPointer(component)>0)
     {
      component.Operation();
     }
  }
class ConcreteComponent:public Component
  {
public:
   void              Operation(void);
  };
void ConcreteComponent::Operation(void)
  {
   Print("The concrete operation");
  }
class ConcreteDecoratorA:public Decorator
  {
protected:
   string            added_state;
public:
                     ConcreteDecoratorA(void);
   void              Operation(void);
  };
ConcreteDecoratorA::ConcreteDecoratorA(void):
   added_state("The added state()")
  {
  }
void ConcreteDecoratorA::Operation(void)
  {
   Decorator::Operation();
   Print(added_state);
  }
class ConcreteDecoratorB:public Decorator
  {
public:
   void              AddedBehavior(void);
   void              Operation(void);
  };
void ConcreteDecoratorB::AddedBehavior(void)
  {
   Print("The added behavior()");
  }
void ConcreteDecoratorB::Operation(void)
  {
   Decorator::Operation();
   AddedBehavior();
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Component* component=new ConcreteComponent();
   Decorator* decorator_a=new ConcreteDecoratorA();
   Decorator* decorator_b=new ConcreteDecoratorB();
   decorator_a.component=component;
   decorator_b.component=decorator_a;
   decorator_b.Operation();
   delete component;
   delete decorator_a;
   delete decorator_b;
  }
}


Фасад (Facade)

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

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

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

Посмотрите на блок-схему, показывающую структуру работы паттерна проектирования Facade (Фасад):

Фасад (Facade)

Как видно на схеме, структура паттерна состоит из следующих элементов:

  • Facade (Фасад) — знает, какая подсистема может запрашивать, и делегирует запросы клиента подходящим объектам подсистемы.
  • Классы подсистемы — выполняют функции подсистемы, при получении запроса от Фасада обрабатывают его, не имеют ссылок на Фасад.

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

Шаблон Фасад можно использовать, когда нужно:

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

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

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

Создадим пространство Facade для объявления всего, что нам нужно.

namespace Facade

Declaring the SubSystemA, SubSystemB, and SubSystemC classes

class SubSystemA
  {
public:
   void              Operation(void);
  };
void SubSystemA::Operation(void)
  {
   Print("The operation of the subsystem A");
  }
class SubSystemB
  {
public:
   void              Operation(void);
  };
void SubSystemB::Operation(void)
  {
   Print("The operation of the subsystem B");
  }
class SubSystemC
  {
public:
   void              Operation(void);
  };
void SubSystemC::Operation(void)
  {
   Print("The operation of the subsystem C");
  }

Объявление класса фасада Facade

class Facade
  {
public:
   void              Operation_A_B(void);
   void              Operation_B_C(void);
protected:
   SubSystemA        subsystem_a;
   SubSystemB        subsystem_b;
   SubSystemC        subsystem_c;
  };
void Facade::Operation_A_B(void)
  {
   Print("The facade of the operation of A & B");
   Print("The request of the facade of the subsystem A operation");
   subsystem_a.Operation();
   Print("The request of the facade of the subsystem B operation");
   subsystem_b.Operation();
  }
void Facade::Operation_B_C(void)
  {
   Print("The facade of the operation of B & C");
   Print("The request of the facade of the subsystem B operation");
   subsystem_b.Operation();
   Print("The request of the facade of the subsystem C operation");
   subsystem_c.Operation();
  }

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

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }

Запуск клиента

void Client::Run(void)
  {
   Facade facade;
   Print("The request of client of the facade operation A & B");
   facade.Operation_A_B();
   Print("The request of client of the facade operation B & C");
   facade.Operation_B_C();
  }

Ниже приведен весь код в одном блоке:

namespace Facade
{
class SubSystemA
  {
public:
   void              Operation(void);
  };
void SubSystemA::Operation(void)
  {
   Print("The operation of the subsystem A");
  }
class SubSystemB
  {
public:
   void              Operation(void);
  };
void SubSystemB::Operation(void)
  {
   Print("The operation of the subsystem B");
  }
class SubSystemC
  {
public:
   void              Operation(void);
  };
void SubSystemC::Operation(void)
  {
   Print("The operation of the subsystem C");
  }
class Facade
  {
public:
   void              Operation_A_B(void);
   void              Operation_B_C(void);
protected:
   SubSystemA        subsystem_a;
   SubSystemB        subsystem_b;
   SubSystemC        subsystem_c;
  };
void Facade::Operation_A_B(void)
  {
   Print("The facade of the operation of A & B");
   Print("The request of the facade of the subsystem A operation");
   subsystem_a.Operation();
   Print("The request of the facade of the subsystem B operation");
   subsystem_b.Operation();
  }
void Facade::Operation_B_C(void)
  {
   Print("The facade of the operation of B & C");
   Print("The request of the facade of the subsystem B operation");
   subsystem_b.Operation();
   Print("The request of the facade of the subsystem C operation");
   subsystem_c.Operation();
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Facade facade;
   Print("The request of client of the facade operation A & B");
   facade.Operation_A_B();
   Print("The request of client of the facade operation B & C");
   facade.Operation_B_C();
  }
}


Приспособленец (Flyweight)

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

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

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

Структура шаблона "Приспособленец" (Flyweight) показана на схеме ниже:

Flyweight1

Flyweight2

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

  • Flyweight.
  • ConcreteFlyweight.
  • UnsharedConcreteFlyweight.
  • FlyweightFactory.
  • Client.

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

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

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

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

Давайте теперь напишем MQL5-код этого шаблона:

Начинаем создания области объявления нашего шаблона Flyweight, внутри которого будем объявлять все, что нам нужно.

namespace Flyweight

Для этого используем ключевое слово interface и объявляем Flyweight

interface Flyweight;

Создадим класс Pair с защищенными и открытыми членами в качестве участника

class Pair
  {
protected:
   string            key;
   Flyweight*        value;
public:
                     Pair(void);
                     Pair(string,Flyweight*);
                    ~Pair(void);
   Flyweight*        Value(void);
   string            Key(void);
  };
Pair::Pair(void){}
Pair::Pair(string a_key,Flyweight *a_value):
   key(a_key),
   value(a_value){}
Pair::~Pair(void)
  {
   delete value;
  }
string Pair::Key(void)
  {
   return key;
  }
Flyweight* Pair::Value(void)
  {
   return value;
  }

Создадим класс Reference и определим его конструктор и деструктор.

class Reference
  {
protected:
   Pair*             pairs[];
public:
                     Reference(void);
                    ~Reference(void);
   void              Add(string,Flyweight*);
   bool              Has(string);
   Flyweight*        operator[](string);
protected:
   int               Find(string);
  };
Reference::Reference(void){}
Reference::~Reference(void)
  {
   int total=ArraySize(pairs);
   for(int i=0; i<total; i++)
     {
      Pair* ipair=pairs[i];
      if(CheckPointer(ipair))
        {
         delete ipair;
        }
     }
  }
int Reference::Find(string key)
  {
   int total=ArraySize(pairs);
   for(int i=0; i<total; i++)
     {
      Pair* ipair=pairs[i];
      if(ipair.Key()==key)
        {
         return i;
        }
     }
   return -1;
  }
bool Reference::Has(string key)
  {
   return (Find(key)>-1)?true:false;
  }
void Reference::Add(string key,Flyweight *value)
  {
   int size=ArraySize(pairs);
   ArrayResize(pairs,size+1);
   pairs[size]=new Pair(key,value);
  }
Flyweight* Reference::operator[](string key)
  {
   int find=Find(key);
   return (find>-1)?pairs[find].Value():NULL;
  }

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

interface Flyweight
  {
   void Operation(int extrinsic_state);
  };

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

class ConcreteFlyweight:public Flyweight
  {
public:
   void              Operation(int extrinsic_state);
protected:
   int               intrinsic_state;
  };
void ConcreteFlyweight::Operation(int extrinsic_state)
  {
   intrinsic_state=extrinsic_state;
   printf("The intrinsic state - %d",intrinsic_state);
  }

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

class UnsharedConcreteFlyweight:public Flyweight
  {
protected:
   int               all_state;
public:
   void              Operation(int extrinsic_state);
  };
void UnsharedConcreteFlyweight::Operation(int extrinsic_state)
  {
   all_state=extrinsic_state;
   Print("all state - %d",all_state);
  }

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

class FlyweightFactory
  {
protected:
   Reference        pool;
public:
                     FlyweightFactory(void);
   Flyweight*        Flyweight(string key);
  };
FlyweightFactory::FlyweightFactory(void)
  {
   pool.Add("1",new ConcreteFlyweight);
   pool.Add("2",new ConcreteFlyweight);
   pool.Add("3",new ConcreteFlyweight);
  }
Flyweight* FlyweightFactory::Flyweight(string key)
  {
   if(!pool.Has(key))
     {
      pool.Add(key,new ConcreteFlyweight());
     }
   return pool[key];
  }

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

class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }

Запуск клиента

void Client::Run(void)
  {
   int extrinsic_state=7;
   Flyweight* flyweight;
   FlyweightFactory factory;
   flyweight=factory.Flyweight("1");
   flyweight.Operation(extrinsic_state);
   flyweight=factory.Flyweight("10");
   flyweight.Operation(extrinsic_state);
   flyweight=new UnsharedConcreteFlyweight();
   flyweight.Operation(extrinsic_state);
   delete flyweight;
  }

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

namespace Flyweight
{
interface Flyweight;
class Pair
  {
protected:
   string            key;
   Flyweight*        value;
public:
                     Pair(void);
                     Pair(string,Flyweight*);
                    ~Pair(void);
   Flyweight*        Value(void);
   string            Key(void);
  };
Pair::Pair(void){}
Pair::Pair(string a_key,Flyweight *a_value):
   key(a_key),
   value(a_value){}
Pair::~Pair(void)
  {
   delete value;
  }
string Pair::Key(void)
  {
   return key;
  }
Flyweight* Pair::Value(void)
  {
   return value;
  }
class Reference
  {
protected:
   Pair*             pairs[];
public:
                     Reference(void);
                    ~Reference(void);
   void              Add(string,Flyweight*);
   bool              Has(string);
   Flyweight*        operator[](string);
protected:
   int               Find(string);
  };
Reference::Reference(void){}
Reference::~Reference(void)
  {
   int total=ArraySize(pairs);
   for(int i=0; i<total; i++)
     {
      Pair* ipair=pairs[i];
      if(CheckPointer(ipair))
        {
         delete ipair;
        }
     }
  }
int Reference::Find(string key)
  {
   int total=ArraySize(pairs);
   for(int i=0; i<total; i++)
     {
      Pair* ipair=pairs[i];
      if(ipair.Key()==key)
        {
         return i;
        }
     }
   return -1;
  }
bool Reference::Has(string key)
  {
   return (Find(key)>-1)?true:false;
  }
void Reference::Add(string key,Flyweight *value)
  {
   int size=ArraySize(pairs);
   ArrayResize(pairs,size+1);
   pairs[size]=new Pair(key,value);
  }
Flyweight* Reference::operator[](string key)
  {
   int find=Find(key);
   return (find>-1)?pairs[find].Value():NULL;
  }
interface Flyweight
  {
   void Operation(int extrinsic_state);
  };
class ConcreteFlyweight:public Flyweight
  {
public:
   void              Operation(int extrinsic_state);
protected:
   int               intrinsic_state;
  };
void ConcreteFlyweight::Operation(int extrinsic_state)
  {
   intrinsic_state=extrinsic_state;
   Print("The intrinsic state - %d",intrinsic_state);
  }
class UnsharedConcreteFlyweight:public Flyweight
  {
protected:
   int               all_state;
public:
   void              Operation(int extrinsic_state);
  };
void UnsharedConcreteFlyweight::Operation(int extrinsic_state)
  {
   all_state=extrinsic_state;
   Print("all state - %d",all_state);
  }
class FlyweightFactory
  {
protected:
   Reference        pool;
public:
                     FlyweightFactory(void);
   Flyweight*        Flyweight(string key);
  };
FlyweightFactory::FlyweightFactory(void)
  {
   pool.Add("1",new ConcreteFlyweight);
   pool.Add("2",new ConcreteFlyweight);
   pool.Add("3",new ConcreteFlyweight);
  }
Flyweight* FlyweightFactory::Flyweight(string key)
  {
   if(!pool.Has(key))
     {
      pool.Add(key,new ConcreteFlyweight());
     }
   return pool[key];
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   int extrinsic_state=7;
   Flyweight* flyweight;
   FlyweightFactory factory;
   flyweight=factory.Flyweight("1");
   flyweight.Operation(extrinsic_state);
   flyweight=factory.Flyweight("10");
   flyweight.Operation(extrinsic_state);
   flyweight=new UnsharedConcreteFlyweight();
   flyweight.Operation(extrinsic_state);
   delete flyweight;
  }
}


Заместитель (Proxy)

Мы подошли к последнему паттерну из типов структурных шаблонов проектирования Proxy (заместитель). Этот шаблон имеет много типов с точки зрения репрезентативности. В целом, можно сказать, что Proxy можно использовать для представления альтернативы или заместителя для другого объекта для полного контроля с точки зрения доступа к этому объекту. Также класс этот шаблон называют Surrogate.

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

Этот шаблон предоставляет суррогат для управления доступом к объекту.

Ниже приведена схема структуры шаблона проектирования "Заместитель/Proxy":

Заместитель (Proxy)

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

  • Proxy.
  • Subject.
  • Real subject.

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

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

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

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

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

namespace Proxy

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

class Subject
  {
public:
   virtual void      Request(void)=0;
  };

Создание класса RealSubject

class RealSubject:public Subject
  {
public:
   void              Request(void);
  };
void RealSubject::Request(void)
  {
   Print("The real subject");
  }

Создание класса Proxy в качестве участника

class Proxy:public Subject
  {
protected:
   RealSubject*      real_subject;
public:
                    ~Proxy(void);
   void              Request(void);
  };
Proxy::~Proxy(void)
  {
   delete real_subject;
  }
void Proxy::Request(void)
  {
   if(!CheckPointer(real_subject))
     {
      real_subject=new RealSubject;
     }
   real_subject.Request();
  }

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

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }

Запуск клиента

void Client::Run(void)
  {
   Subject* subject=new Proxy;
   subject.Request();
   delete subject;
  }

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

namespace Proxy
{
class Subject
  {
public:
   virtual void      Request(void)=0;
  };
class RealSubject:public Subject
  {
public:
   void              Request(void);
  };
void RealSubject::Request(void)
  {
   Print("The real subject");
  }
class Proxy:public Subject
  {
protected:
   RealSubject*      real_subject;
public:
                    ~Proxy(void);
   void              Request(void);
  };
Proxy::~Proxy(void)
  {
   delete real_subject;
  }
void Proxy::Request(void)
  {
   if(!CheckPointer(real_subject))
     {
      real_subject=new RealSubject;
     }
   real_subject.Request();
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Subject* subject=new Proxy;
   subject.Request();
   delete subject;
  }
}


Заключение

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

Мы рассмотрели следующие шаблоны структурного проектирования:

  • Адаптер (Adapter)
  • Мост (Bridge)
  • Компоновщик (Composite)
  • Декоратор (Decorator)
  • Фасад (Facade)
  • Приспособленец (Flyweight)
  • Заместитель (Proxy)

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

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

  • 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. Если статья вам понравилась, почитайте и другие материалы. По ссылкам ниже вы можете ознакомиться с другими моими статьями. В частности, вы можете найти серию статей о создании торговых систем на основе самых популярных технических индикаторов, таких как RSI, MACD, Bollinger Bands, Moving averages, Stochastics и другие. Надеюсь, для кого-нибудь они окажутся полезными и помогут поднять разработку программного обеспечения и торговлю на новый уровень.

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

Прикрепленные файлы |
Adapter_Class.mqh (1.07 KB)
Adapter_Object.mqh (0.72 KB)
Bridge.mqh (3.38 KB)
Composite.mqh (3.09 KB)
Decorator.mqh (3.53 KB)
Facade.mqh (3.43 KB)
Flyweight.mqh (3.47 KB)
Proxy.mqh (1.79 KB)
Разрабатываем мультивалютный советник (Часть 5): Переменный размер позиций Разрабатываем мультивалютный советник (Часть 5): Переменный размер позиций
В предыдущих частях разрабатываемый советник имел возможность использовать только фиксированный размер позиций для торговли. Это допустимо для тестирования, но нежелательно при торговле на реальном счёте. Давайте обеспечим возможность торговли с переменным размером позиций.
Комбинаторно-симметричная перекрестная проверка в MQL5 Комбинаторно-симметричная перекрестная проверка в MQL5
В статье показана реализация комбинаторно-симметричной перекрестной проверки на чистом MQL5 для измерения степени подгонки после оптимизации стратегии с использованием медленного полного алгоритма тестера стратегий.
Создаем простой мультивалютный советник с использованием MQL5 (Часть 4): Треугольная скользящая средняя — Сигналы индикатора Создаем простой мультивалютный советник с использованием MQL5 (Часть 4): Треугольная скользящая средняя — Сигналы индикатора
Под мультивалютным советником в этой статье понимается советник, или торговый робот, который может торговать (открывать/закрывать ордера, управлять ордерами, например, трейлинг-стоп-лоссом и трейлинг-профитом) более чем одной парой символов с одного графика. На этот раз мы будем использовать только один индикатор, а именно треугольную скользящую среднюю на одном или нескольких таймфреймах.
Популяционные алгоритмы оптимизации: Алгоритм оптимизации китов (Whale Optimization Algorithm, WOA) Популяционные алгоритмы оптимизации: Алгоритм оптимизации китов (Whale Optimization Algorithm, WOA)
Алгоритм оптимизации китов (WOA) - это метаэвристический алгоритм, вдохновленный поведением и охотничьими стратегиями горбатых китов. Основная идея WOA заключается в имитации так называемого "пузырькового сетевого" метода кормления, при котором киты создают пузыри вокруг добычи, чтобы затем нападать на нее в спиральном движении.