Шаблоны проектирования в MQL5 (Часть I): Порождающие шаблоны (Creational Patterns)

Mohamed Abdelmaaboud | 13 марта, 2024

Введение

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

Ответ — да, существуют способы, а точнее, шаблоны, которые можно использовать при решении конкретных проблем. Это "шаблоны проектирования" (Design Patterns). Шаблоны проектирования поддерживают концепцию ООП DRY ("не повторяйся"), что означает, что не обязательно каждый раз изобретать велосипед. Если есть задача и есть шаблон, который можно использовать для ее эффективного решения, просто используем этот шаблон и экономим время и силы.

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

В статье рассмотрим следующие темы:

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

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

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

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


Определение шаблонов проектирования

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

Исходя из целей использования можно выделить три типа шаблонов:

Порождающие шаблоны (Creational Patterns)

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

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

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

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

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

Типы порождающих шаблонов:

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


Абстрактная фабрика

Абстрактная фабрика — один из порождающих шаблонов. Этот шаблон состоит из фабрик (Factory) и продуктов (Product) и используется для создания семейств связанных объектов-продуктов без прямого создания экземпляров классов. Его можно использовать, когда количество и общие типы объектов продукта постоянны, но различаются в отдельных семействах продуктов.

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

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

Абстрактная фабрика

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

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

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

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

Примеры использования такого Абстрактной фабрики:

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

Давайте посмотрим, как можно реализовать структуру Абстрактной фабрики в виде include-файла. Мы в статье рассмотрим одну структуру, но вы можете выбирать собственную реализацию с учетом ваших потребностей.

Итак, пишем код структуры по шагам:

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

namespace AbstractFactory

Используя ключевое слово interface, объявляем функцию AbstractProductA.

interface AbstractProductA
  {
  };

Далее, снова с ключевым словом interface объявляем функцию AbstractProductB с переменной void Interact в теле функции.

interface AbstractProductB
  {
   void Interact(AbstractProductA*);
  };

Объявляем интерфейса для операций по созданию абстрактных продуктов

interface AbstractFactory
  {
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };

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

class ProductA1:public AbstractProductA
  {
public:
                     ProductA1(void);
  };
void ProductA1::ProductA1(void) 
{
Print("Product A1 is constructed");
}
class ProductA2:public AbstractProductA
  {
public:
                     ProductA2(void);
  };
void ProductA2::ProductA2(void) 
{
Print("Product A2 is constructed");
}

Конструируем конкретные продукты B1 и B2, а затем разрешаем взаимодействие с абстрактным продуктом A.

class ProductB1:public AbstractProductB
  {
public:
                     ProductB1(void);
   void              Interact(AbstractProductA*);
  };
void ProductB1::ProductB1(void) 
{
Print("Product B1 is constructed");
}
void ProductB1::Interact(AbstractProductA*src)
  {
   Print("Product B1: ",&this," is interacting with Product A: ",src);
  }
class ProductB2:public AbstractProductB
  {
public:
                     ProductB2(void);
   void              Interact(AbstractProductA*);
  };
void ProductB2::ProductB2(void) 
{
Print("Product B2 is constructed");
}
void ProductB2::Interact(AbstractProductA*src)
  {
   Print("Product B2: ",&this," is interacting with Product A: ",src);
  }

Объявляем конкретные фабрики Factory1 и Factory2, они создают и возвращают продукты А1, А2, В1, В2

class Factory1:public AbstractFactory
  {
public:
                     Factory1(void);
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };
void Factory1::Factory1(void)
  {
   Print("Factory 1: ",&this," is constructed");
  }
AbstractProductA* Factory1::CreateProductA(void)
  {
   Print("Factory 1 creates and returns Product A1");
   return new ProductA1;
  }
AbstractProductB* Factory1::CreateProductB(void)
  {
   Print("Factory 1 creates and returns Product B1");
   return new ProductB1;
  }
class Factory2:public AbstractFactory
  {
public:
                     Factory2(void);
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };
void Factory2::Factory2(void)
  {
   Print("Factory 2: ",&this," is constructed");
  }
AbstractProductA* Factory2::CreateProductA(void)
  {
   Print("Factory 2 creates and returns Product A2");
   return new ProductA2;
  }
AbstractProductB* Factory2::CreateProductB(void)
  {
   Print("Factory 2 creates and returns Product B2");
   return new ProductB2;
  }

Объявляем класс FactoryClient и используем интерфейсы, объявленные абстрактной фабрикой и абстрактным продуктом.

class FactoryClient
  {
public:
   void              Run(void);
   void              Switch(AbstractFactory*);
                     FactoryClient(AbstractFactory*);
                    ~FactoryClient(void);
protected:
   AbstractProductA* apa;
   AbstractProductB* apb;
   AbstractFactory*  factory;
   void              Delete(void);
  };
void FactoryClient::FactoryClient(AbstractFactory* af)
  {
   Print("Factory client created and received Abstract Factory ",af);
   Print("Factory client requests to accept/switch the factories");
   Switch(af);
  }
void FactoryClient::~FactoryClient(void)
  {
   Delete();
  }
void FactoryClient::Run(void)
  {
   Print("Factory client runs the abstract Product B");
   apb.Interact(apa);
  }
void FactoryClient::Delete(void)
  {
   delete apa;
   delete apb;
   delete factory;
  }
void FactoryClient::Switch(AbstractFactory *af)
  {
   string sFactory;
   StringConcatenate(sFactory,sFactory,factory);
   int iFactory=(int)StringToInteger(sFactory);
   if(iFactory>0)
     {
      Print("Factory client switches the old factory ",factory," to the new one ",af);
     }
   else
     {
      Print("Factory client accepts the new factory ",af);
     }
   Delete();
   factory=af;
   Print("Factory client saved the new factory");
   Print("Factory client requests its new factory to create the Product A");
   apa=factory.CreateProductA();
   Print("Factory client requests its new factory to create the Product B");
   apb=factory.CreateProductB();
  }

Определяем класс Client и запускаем шаблон 

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Print("The client requests to create the Factory 1");
   Print("The client requests to create the Factory client");
   Print("The client requests the Factory client to manage the Factory 1");
   FactoryClient client(new Factory1);
   Print("The client requests the Factory client to operate");
   client.Run();
   Print("The client requests to create the new factory 2 and asks the factory client to switch factories");
   client.Switch(new Factory2);
   Print("The client requests the Factory client to run again");
   client.Run();
  }

Вот такой блок кода у нас получился:

namespace AbstractFactory
{
interface AbstractProductA
  {
  };
interface AbstractProductB
  {
   void Interact(AbstractProductA*);
  };
interface AbstractFactory
  {
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };
class ProductA1:public AbstractProductA
  {
public:
                     ProductA1(void);
  };
void ProductA1::ProductA1(void) 
{
Print("Product A1 is constructed");
}
class ProductA2:public AbstractProductA
  {
public:
                     ProductA2(void);
  };
void ProductA2::ProductA2(void) 
{
Print("Product A2 is constructed");
}
class ProductB1:public AbstractProductB
  {
public:
                     ProductB1(void);
   void              Interact(AbstractProductA*);
  };
void ProductB1::ProductB1(void) 
{
Print("Product B1 is constructed");
}
void ProductB1::Interact(AbstractProductA*src)
  {
   Print("Product B1: ",&this," is interacting with Product A: ",src);
  }
class ProductB2:public AbstractProductB
  {
public:
                     ProductB2(void);
   void              Interact(AbstractProductA*);
  };
void ProductB2::ProductB2(void) 
{
Print("Product B2 is constructed");
}
void ProductB2::Interact(AbstractProductA*src)
  {
   Print("Product B2: ",&this," is interacting with Product A: ",src);
  }
class Factory1:public AbstractFactory
  {
public:
                     Factory1(void);
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };
void Factory1::Factory1(void)
  {
   Print("Factory 1: ",&this," is constructed");
  }
AbstractProductA* Factory1::CreateProductA(void)
  {
   Print("Factory 1 creates and returns Product A1");
   return new ProductA1;
  }
AbstractProductB* Factory1::CreateProductB(void)
  {
   Print("Factory 1 creates and returns Product B1");
   return new ProductB1;
  }
class Factory2:public AbstractFactory
  {
public:
                     Factory2(void);
   AbstractProductA* CreateProductA(void);
   AbstractProductB* CreateProductB(void);
  };
void Factory2::Factory2(void)
  {
   Print("Factory 2: ",&this," is constructed");
  }
AbstractProductA* Factory2::CreateProductA(void)
  {
   Print("Factory 2 creates and returns Product A2");
   return new ProductA2;
  }
AbstractProductB* Factory2::CreateProductB(void)
  {
   Print("Factory 2 creates and returns Product B2");
   return new ProductB2;
  }
class FactoryClient
  {
public:
   void              Run(void);
   void              Switch(AbstractFactory*);
                     FactoryClient(AbstractFactory*);
                    ~FactoryClient(void);
protected:
   AbstractProductA* apa;
   AbstractProductB* apb;
   AbstractFactory*  factory;
   void              Delete(void);
  };
void FactoryClient::FactoryClient(AbstractFactory* af)
  {
   Print("Factory client created and received Abstract Factory ",af);
   Print("Factory client requests to accept/switch the factories");
   Switch(af);
  }
void FactoryClient::~FactoryClient(void)
  {
   Delete();
  }
void FactoryClient::Run(void)
  {
   Print("Factory client runs the abstract Product B");
   apb.Interact(apa);
  }
void FactoryClient::Delete(void)
  {
   delete apa;
   delete apb;
   delete factory;
  }
void FactoryClient::Switch(AbstractFactory *af)
  {
   string sFactory;
   StringConcatenate(sFactory,sFactory,factory);
   int iFactory=(int)StringToInteger(sFactory);
   if(iFactory>0)
     {
      Print("Factory client switches the old factory ",factory," to the new one ",af);
     }
   else
     {
      Print("Factory client accepts the new factory ",af);
     }
   Delete();
   factory=af;
   Print("Factory client saved the new factory");
   Print("Factory client requests its new factory to create the Product A");
   apa=factory.CreateProductA();
   Print("Factory client requests its new factory to create the Product B");
   apb=factory.CreateProductB();
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Print("The client requests to create the Factory 1");
   Print("The client requests to create the Factory client");
   Print("The client requests the Factory client to manage the Factory 1");
   FactoryClient client(new Factory1);
   Print("The client requests the Factory client to operate");
   client.Run();
   Print("The client requests to create the new factory 2 and asks the factory client to switch factories");
   client.Switch(new Factory2);
   Print("The client requests the Factory client to run again");
   client.Run();
  }
}


Строитель

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

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

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

Строитель

Как видите, в зависимости от структуры у нас есть Builder, который определяет интерфейс для создания частей объекта продукта, Director, который создает объект с использованием интерфейса Builder, и ConcreteBuilder, который можно использовать для выполнения следующих действий:

В итоге имеем продукт, который представляет строящийся сложный объект.

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

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

Что он позволяет делать

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

Теперь давайте рассмотрим структуру реализации паттерна "Строитель".

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

namespace Builder

В теле функции создаем класс продукта Product.

class Product
  {
public:
   void              Add(string);
   void              Show();
protected:
   string            parts[];
  };

Добавляем деталь

void Product::Add(string part)
  {
   int size=ArraySize(parts);
   ArrayResize(parts,size+1);
   parts[size]=part;
   Print("The product added ",part," to itself");
  }

Показываем все детали продукта

void Product::Add(string part)
  {
   int size=ArraySize(parts);
   ArrayResize(parts,size+1);
   parts[size]=part;
   Print("The product added ",part," to itself");
  }

Создаем абстрактный интерфейс строителя Builder для создания частей продукта A, B и C.

interface Builder
  {
   void BuildPartA();
   void BuildPartB();
   void BuildPartC();
   Product* GetResult();
  };

Создаем класс Director, который конструирует объект с интерфейсом Builder с помощью функции класса.

class Director
  {
public:
   void              Construct();
                     Director(Builder*);
                    ~Director();
protected:
   Builder*          builder;
  };

Создание Builder и получение его в Director

void Director::Director(Builder *b)
  {
   builder=b;
   Print("The director created and received the builder ",b);
  }
void Director::~Director(void)
  {
   delete builder;
  }

Директор (Director) начинает создавать части продукта A, B и C.

void Director::Construct(void)
  {
   Print("The director started the construction");
   Print("The director requestd its builder to build the product parts");
   builder.BuildPartA();
   builder.BuildPartB();
   builder.BuildPartC();
   Print("The director's builder constructed the product from parts");
  }

Создаем класс ConcreteBuilder с тремя общедоступными членами для частей продукта и одним защищенным членом для продукта.

class ConcreteBuilder:public Builder
  {
public:
   void              BuildPartA();
   void              BuildPartB();
   void              BuildPartC();
   Product*          GetResult();
protected:
   Product           product;
  };

Строитель добавляет детали A, B и C в продукт, а затем возвращаем продукт с помощью следующих функций.

void ConcreteBuilder::BuildPartA(void)
  {
   Print("The builder requests the product to add part A to itself");
   product.Add("part a");
   Print("The builder made the part of A and added it to the product");
  }
void ConcreteBuilder::BuildPartB(void)
  {
   Print("The builder requests the product to add part B to itself");
   product.Add("part b");
   Print("The builder made the part of B and added it to the product");
  }
void ConcreteBuilder::BuildPartC(void)
  {
   Print("The builder requests the product to add part C to itself");
   product.Add("part c");
   Print("The builder made part C and added it to the product");
  }
Product* ConcreteBuilder::GetResult(void)
  {
   Print("The builder is returns the product");
   return &product;
  }

Создаем класс клиента Client с двумя общедоступными членами для конструкторов Output и Run и запускаем клиента. 

class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output() {return __FUNCTION__;}
void Client::Run()
  {
   Print("The client requests to create a new concrete builder");
   Builder* builder=new ConcreteBuilder;
   Print("The client requests to create a director and give him the builder");
   Director director(builder);
   Print("The client requests the director to perform the construction");
   director.Construct();
   Print("The client requests the builder to return the result product");
   Product* product=builder.GetResult();
   Print("The client is requests the product to describe itself");
   product.Show();
  }

Ниже представлен код для структуры Builder целиком в одном блоке.

namespace Builder
{
class Product
  {
public:
   void              Add(string);
   void              Show();
protected:
   string            parts[];
  };
void Product::Add(string part)
  {
   int size=ArraySize(parts);
   ArrayResize(parts,size+1);
   parts[size]=part;
   Print("The product added ",part," to itself");
  }
void Product::Show(void)
  {
   Print("The product shows all parts that it is made of");
   int total=ArraySize(parts);
   for(int i=0; i<total; i++)
      Print(parts[i]);
  }
interface Builder
  {
   void BuildPartA();
   void BuildPartB();
   void BuildPartC();
   Product* GetResult();
  };
class Director
  {
public:
   void              Construct();
                     Director(Builder*);
                    ~Director();
protected:
   Builder*          builder;
  };
void Director::Director(Builder *b)
  {
   builder=b;
   Print("The director created and received the builder ",b);
  }
void Director::~Director(void)
  {
   delete builder;
  }
void Director::Construct(void)
  {
   Print("The director started the construction");
   Print("The director requestd its builder to build the product parts");
   builder.BuildPartA();
   builder.BuildPartB();
   builder.BuildPartC();
   Print("The director's builder constructed the product from parts");
  }
class ConcreteBuilder:public Builder
  {
public:
   void              BuildPartA();
   void              BuildPartB();
   void              BuildPartC();
   Product*          GetResult();
protected:
   Product           product;
  };
void ConcreteBuilder::BuildPartA(void)
  {
   Print("The builder requests the product to add part A to itself");
   product.Add("part a");
   Print("The builder made the part of A and added it to the product");
  }
void ConcreteBuilder::BuildPartB(void)
  {
   Print("The builder requests the product to add part B to itself");
   product.Add("part b");
   Print("The builder made the part of B and added it to the product");
  }
void ConcreteBuilder::BuildPartC(void)
  {
   Print("The builder requests the product to add part C to itself");
   product.Add("part c");
   Print("The builder made part C and added it to the product");
  }
Product* ConcreteBuilder::GetResult(void)
  {
   Print("The builder is returns the product");
   return &product;
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output() {return __FUNCTION__;}
void Client::Run()
  {
   Print("The client requests to create a new concrete builder");
   Builder* builder=new ConcreteBuilder;
   Print("The client requests to create a director and give him the builder");
   Director director(builder);
   Print("The client requests the director to perform the construction");
   director.Construct();
   Print("The client requests the builder to return the result product");
   Product* product=builder.GetResult();
   Print("The client is requests the product to describe itself");
   product.Show();
  }
}


Фабричный метод

Порождающий шаблон "Фабричный метод" определяет интерфейс для создания объекта и дает подклассам разрешение выбирать класс для создания экземпляра, а также позволяет классу откладывать создание экземпляра для подклассов. Этот паттерн также называют виртуальным конструктором (Virtual Constructor).

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

Схема структуры фабричного метода:

Фабричный метод

Итак, на этой схеме имеем следующие элементы:

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

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

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

Далее рассмотрим, как реализовать шаблон Фабричного метода в виде подключаемого файла на MQL5.

Используя ключевое слово namespace, объявляем FactoryMethod

namespace FactoryMethod

Создаем интерфейс объекта Product

interface Product
  {
  };

Создаем класс ConcreteProduct и реализуем интерфейс продукта

class ConcreteProduct:public Product
  {
public:
                     ConcreteProduct(void);
  };
ConcreteProduct::ConcreteProduct(void)
  {
   "The concrete product: ",&this," created");
  }

Создаем класс Creator, возвращаем объекта типа продукта, реализуем возврат конкретного продукта, создаем объект продукта

class Creator
  {
public:
   virtual Product*  FactoryMethod(void)=0;
   void              AnOperation(void);
                    ~Creator(void);
protected:
   Product*          product;
  };
Creator::~Creator(void) {delete product;}
void Creator::AnOperation(void)
  {
   Print("The creator runs its operation");
   delete product;
   product=FactoryMethod();
   Print("The creator saved the product that received from the virtual factory method");
  }

Запускаем фабричный метод, создаем и возвращаем новый конкретный продукт

class ConcreteCreator:public Creator
  {
public:
   Product*          FactoryMethod(void);
  };
Product* ConcreteCreator::FactoryMethod(void)
  {
   Print("The creator runs the factory method");
   Print("The concrete creator creates and returns the new concrete product");
   return new ConcreteProduct;
  }

Создаем класс Client с двумя публичными членами Output и Run.

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

Запускаем класс Client для запроса создания создателя Creator, возвращаем продукт, запускаем работу создателя.

void Client::Run(void)
  {
   Print("requests to make the creator");
   ConcreteCreator creator;
   Print("requests the creator to run its factory method to return the product");
   Product* product=creator.FactoryMethod();
   Print("requests the creator to run its operation");
   creator.AnOperation();
   delete product;
  }

Ниже приведен полный код в одном блоке, демонстрирующий структуру фабричного метода.

namespace FactoryMethod
{
interface Product
  {
  };
class ConcreteProduct:public Product
  {
public:
                     ConcreteProduct(void);
  };
ConcreteProduct::ConcreteProduct(void)
  {
   Print("The concrete product: ",&this," created");
  }
class Creator
  {
public:
   virtual Product*  FactoryMethod(void)=0;
   void              AnOperation(void);
                    ~Creator(void);
protected:
   Product*          product;
  };
Creator::~Creator(void) {delete product;}
void Creator::AnOperation(void)
  {
   Print("The creator runs its operation");
   delete product;
   product=FactoryMethod();
   Print("The creator saved the product that received from the virtual factory method");
  }
class ConcreteCreator:public Creator
  {
public:
   Product*          FactoryMethod(void);
  };
Product* ConcreteCreator::FactoryMethod(void)
  {
   Print("The creator runs the factory method");
   Print("The concrete creator creates and returns the new concrete product");
   return new ConcreteProduct;
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Print("requests to make the creator");
   ConcreteCreator creator;
   Print("requests the creator to run its factory method to return the product");
   Product* product=creator.FactoryMethod();
   Print("requests the creator to run its operation");
   creator.AnOperation();
   delete product;
  }
}


Прототип

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

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

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

Прототип

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

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

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

Что нам дает использование паттерна Прототип:

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

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

Используем ключевое слово namespace, объявляем функцию Prototype.

namespace Prototype

Создаем класс или интерфейс Prototype для клонирования самого себя.

class Prototype
  {
public:
   virtual Prototype* Clone(void)=0;
                     Prototype(int);
protected:
   int               id;
  };
Prototype::Prototype(int i):id(i)
  {
   Print("The prototype ",&this,", id - ",id," is created");
  }

Создаем конкретные прототипы concretePrototype1 и concretePrototype2 - здесь реализуется операция клонирования самих себя.

class ConcretePrototype1:public Prototype
  {
public:
                     ConcretePrototype1(int);
   Prototype*        Clone(void);
  };
ConcretePrototype1::ConcretePrototype1(int i):
   Prototype(i)
  {
   Print("The concrete prototype 1 - ",&this,", id - ",id," is created");
  }
Prototype* ConcretePrototype1::Clone(void)
  {
   Print("The cloning concrete prototype 1 - ",&this,", id - ",id);
   return new ConcretePrototype1(id);
  }
class ConcretePrototype2:public Prototype
  {
public:
                     ConcretePrototype2(int);
   Prototype*        Clone(void);
  };
ConcretePrototype2::ConcretePrototype2(int i):
   Prototype(i)
  {
   Print("The concrete prototype 2 - ",&this,", id - ",id," is created");
  }
Prototype* ConcretePrototype2::Clone(void)
  {
   Print("The cloning concrete prototype 2 - ",&this,", id - ",id);
   return new ConcretePrototype2(id);
  }

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

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

Запуск клиента, который просит прототип клонировать себя

void Client::Run(void)
  {
   Prototype* prototype;
   Prototype* clone;
   Print("requests to create the concrete prototype 1 with id 1");
   prototype=new ConcretePrototype1(1);
   Print("requests the prototype ",prototype," to create its clone");
   clone=prototype.Clone();
   delete prototype;
   delete clone;
   Print("requests to create the concrete prototype 2 with id 2");
   prototype=new ConcretePrototype2(2);
   Print("requests the prototype ",prototype," to create its clone");
   clone=prototype.Clone();
   delete prototype;
   delete clone;
  }

Полный код структуры паттерна "Прототип" одним блоком целиком:

namespace Prototype
{
class Prototype
  {
public:
   virtual Prototype* Clone(void)=0;
                     Prototype(int);
protected:
   int               id;
  };
Prototype::Prototype(int i):id(i)
  {
   Print("The prototype ",&this,", id - ",id," is created");
  }
class ConcretePrototype1:public Prototype
  {
public:
                     ConcretePrototype1(int);
   Prototype*        Clone(void);
  };
ConcretePrototype1::ConcretePrototype1(int i):
   Prototype(i)
  {
   Print("The concrete prototype 1 - ",&this,", id - ",id," is created");
  }
Prototype* ConcretePrototype1::Clone(void)
  {
   Print("The cloning concrete prototype 1 - ",&this,", id - ",id);
   return new ConcretePrototype1(id);
  }
class ConcretePrototype2:public Prototype
  {
public:
                     ConcretePrototype2(int);
   Prototype*        Clone(void);
  };
ConcretePrototype2::ConcretePrototype2(int i):
   Prototype(i)
  {
   Print("The concrete prototype 2 - ",&this,", id - ",id," is created");
  }
Prototype* ConcretePrototype2::Clone(void)
  {
   Print("The cloning concrete prototype 2 - ",&this,", id - ",id);
   return new ConcretePrototype2(id);
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Prototype* prototype;
   Prototype* clone;
   Print("requests to create the concrete prototype 1 with id 1");
   prototype=new ConcretePrototype1(1);
   Print("requests the prototype ",prototype," to create its clone");
   clone=prototype.Clone();
   delete prototype;
   delete clone;
   Print("requests to create the concrete prototype 2 with id 2");
   prototype=new ConcretePrototype2(2);
   Print("requests the prototype ",prototype," to create its clone");
   clone=prototype.Clone();
   delete prototype;
   delete clone;
  }
}


Одиночка

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

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

Структура шаблона "Одиночка" (Singleton) показана на схеме ниже:

Одиночка

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

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

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

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

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

По уже известной нам схеме разработаем код шаблона Одиночка на MQL5.

Используем ключевое слово namespace, объявляем функцию Singleton.

namespace Singleton

Создаем класс Singleton

class Singleton
  {
public:
   static Singleton* Instance(void);
   void              SingletonOperation(void);
   string            GetSingletonData(void);
protected:
                     Singleton(void);
   static Singleton* uniqueInstance;
   string            singletonData;
  };
Singleton* Singleton::uniqueInstance=NULL;

Создание объекта Singleton

Singleton::Singleton(void)
  {
   Print("The singleton ",&this," is created");
  }

Запуск операции Singleton и установка ее данных

void Singleton::SingletonOperation(void)
  {
   Print("runs the singleton operation > setting singleton data");
   singletonData="singleton data";
  }

Чтение и получение данных Singleton

string Singleton::GetSingletonData(void)
  {
   Print("reads and returns the singleton data");
   return singletonData;
  }

Получение или возврат уникального экземпляра

Singleton* Singleton::Instance(void)
  {
   Print("The singleton instance method runs");
   if(!CheckPointer(uniqueInstance))
     {
      Print("The unique instance of the singleton is an empty");
      uniqueInstance=new Singleton;
      Print("singleton assigned to unique instance");
     }
   Print("The unique instance contains singleton: ",uniqueInstance);
   Print("returns the unique instance ",uniqueInstance," of the singleton");
   return uniqueInstance;
  }

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

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

Клиент Client получает доступ к одиночке Singleton через экземпляр

void Client::Run(void)
  {
   Print("requests the singleton instance 1");
   Singleton* instance1=Singleton::Instance();
   Print("requests the singleton instance 2");
   Singleton* instance2=Singleton::Instance();
   string compareInstances=
      (instance1==instance2)?
      "instances 1 and instance 2 are the same objects":
      "instances are different objects";
   Print(compareInstances);
   Print("requests singleton operation on the instance 1");
   instance1.SingletonOperation();
   Print("requests singleton data by the singleton instance 2");
   string singletonData=instance2.GetSingletonData();
   Print(singletonData);
   delete instance1;
  }

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

namespace Singleton
{
class Singleton
  {
public:
   static Singleton* Instance(void);
   void              SingletonOperation(void);
   string            GetSingletonData(void);
protected:
                     Singleton(void);
   static Singleton* uniqueInstance;
   string            singletonData;
  };
Singleton* Singleton::uniqueInstance=NULL;
Singleton::Singleton(void)
  {
   Print("The singleton ",&this," is created");
  }
void Singleton::SingletonOperation(void)
  {
   Print("runs the singleton operation > setting singleton data");
   singletonData="singleton data";
  }
string Singleton::GetSingletonData(void)
  {
   Print("reads and returns the singleton data");
   return singletonData;
  }
Singleton* Singleton::Instance(void)
  {
   Print("The singleton instance method runs");
   if(!CheckPointer(uniqueInstance))
     {
      Print("The unique instance of the singleton is an empty");
      uniqueInstance=new Singleton;
      Print("singleton assigned to unique instance");
     }
   Print("The unique instance contains singleton: ",uniqueInstance);
   Print("returns the unique instance ",uniqueInstance," of the singleton");
   return uniqueInstance;
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Print("requests the singleton instance 1");
   Singleton* instance1=Singleton::Instance();
   Print("requests the singleton instance 2");
   Singleton* instance2=Singleton::Instance();
   string compareInstances=
      (instance1==instance2)?
      "instances 1 and instance 2 are the same objects":
      "instances are different objects";
   Print(compareInstances);
   Print("requests singleton operation on the instance 1");
   instance1.SingletonOperation();
   Print("requests singleton data by the singleton instance 2");
   string singletonData=instance2.GetSingletonData();
   Print(singletonData);
   delete instance1;
  }
}


Заключение

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

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

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

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

Что можно почитать еще по этой теме:

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