English 中文 Español Deutsch 日本語 Português
preview
Шаблоны проектирования в MQL5 (Часть I): Порождающие шаблоны (Creational Patterns)

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

MetaTrader 5Трейдинг | 13 марта 2024, 13:57
679 3
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Введение

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

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

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

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

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

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

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

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


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

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

  • Имя — название шаблона для решения конкретной и описанной проблемы.
  • Проблема — описанная, повторяющаяся и конкретная проблема, с которой мы можем столкнуться.
  • Решение — описанное решение конкретной описанной проблемы.
  • Последствия — результаты применения шаблона для решения проблемы.

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

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

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

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

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

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

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

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

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

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

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

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

  • Что делает шаблон?
  • Какую проблему проектирования он решает?
  • Как его использовать в 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, который можно использовать для выполнения следующих действий:

  • Конструирование и сборка частей изделия посредством реализации интерфейса Builder.
  • Определение представление и его мониторинг.
  • Получение продукта путем предоставления интерфейса.

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

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

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

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

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

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

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

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

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

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

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

  • Product (продукт) — определяет интерфейс объектов, созданных фабричным методом.
  • ConcreteProduct (конкретный продукт) — отвечает за реализацию интерфейса продукта.
  • Creator (создатель) — возвращает объект продукта после объявления фабричного метода, может предоставить реализацию по умолчанию для фабричного метода, который возвращает объект по умолчанию ConcreteProduct, и может создать объект продукта, вызвав фабрику метод.
  • ConcreteCreator (конкретный создатель) — возвращает экземпляр конкретного продукта ConcreteProduct, переопределив фабричный метод.

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

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

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

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


Прототип

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

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

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

Прототип

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

  • Прототип (Prototype) — создает интерфейс, который можно клонировать.
  • Конкретный прототип (ConcretePrototype) — клонирует себя, реализуя для этого операцию.
  • Клиент (Client) — просит прототип клонировать себя для создания нового объекта.

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

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

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

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

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

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


Заключение

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

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

  • Абстрактная фабрика
  • Строитель
  • Фабричный метод
  • Прототип
  • Одиночка

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

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

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

  • Design Patterns - Elements of Reusable Object-Oriented Software by Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides
  • Design Patterns for Dummies by Steve Holzner
  • Head First Design Patterns by Eric Freeman, Elisabeth Robson, Bert Bates, and Kathy Sierra

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

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

Прикрепленные файлы |
Builder.mqh (3 KB)
Factory_Method.mqh (1.51 KB)
Prototype.mqh (3.78 KB)
Singleton.mqh (2.01 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (3)
Denis Kirichenko
Denis Kirichenko | 22 мар. 2024 в 09:56

Думал, переводчики немного накосячили в подразделе про абстрактную фабрику, ан нет - сам автор.

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

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

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

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

В англ.исходнике так:

What design problem does it solve?

So, we can use this pattern in the case of the following:

  • We need an independent system.
  • We need a configured system with one of many families of products.
  • We need to use a family of related product objects together as per its design and enforce this constraint.
  • We need to reveal just interfaces of the provided class, not their implementation.


Rashid Umarov
Rashid Umarov | 22 мар. 2024 в 12:12
Спасибо, поправили
Denis Kirichenko
Denis Kirichenko | 23 мар. 2024 в 16:50

Ещё немного поворчу...

Чтобы быть точным в терминологии, буду попутно смотреть англ. исходник статьи. Итак, про каждый шаблон автор пишет "How we can use it in MQL5?" (с англ. "Как его использовать в MQL5?" ). Тут стоит отметить, что MQL5 - это прикладной специализированный язык. И что же? Действительно  узнаем из материала, как можно использовать шаблоны в MQL5? Нет! Видим, что просто идёт реализация шаблона на MQL5. Имхо, раз это шаблон, стоило бы сначала описать его на псевдокоде, а только потом на MQL5. Ну и в идеале интересно посмотреть на практические примеры использования шаблонов проектирования в MQL5. Не знаю, может я забегаю впереди паровоза, и автор планирует рассматривать каждый шаблон в отдельном опусе. Но пока имеем то, что имеем...



Гибридизация популяционных алгоритмов. Последовательная и параллельная схема Гибридизация популяционных алгоритмов. Последовательная и параллельная схема
В статье мы погрузимся в мир гибридизации алгоритмов оптимизации, рассмотрев три ключевых типа: смешивание стратегий, последовательную и параллельную гибридизации. Мы проведем серию экспериментов, сочетая и тестируя соответствующие алгоритмы оптимизации.
Популяционные алгоритмы оптимизации: Устойчивость к застреванию в локальных экстремумах (Часть II) Популяционные алгоритмы оптимизации: Устойчивость к застреванию в локальных экстремумах (Часть II)
Продолжение эксперимента, цель которого - исследовать поведение популяционных алгоритмов оптимизации в контексте их способности эффективно покидать локальные минимумы при низком разнообразии в популяции и достигать глобальных максимумов. Результаты исследования.
Создаем простой мультивалютный советник с использованием MQL5 (Часть 3): Префиксы/суффиксы символов и торговая сессия Создаем простой мультивалютный советник с использованием MQL5 (Часть 3): Префиксы/суффиксы символов и торговая сессия
Я получил комментарии от нескольких коллег-трейдеров о том, как использовать рассматриваемый мной мультивалютный советник у брокеров, использующих префиксы и/или суффиксы с именами символов, а также о том, как реализовать в советнике торговые часовые пояса или торговые сессии.
Нейросети — это просто (Часть 80): Генеративно-состязательная модель Трансформера графов (GTGAN) Нейросети — это просто (Часть 80): Генеративно-состязательная модель Трансформера графов (GTGAN)
В данной статье я предлагаю Вам познакомиться с алгоритмом GTGAN, который был представлен в январе 2024 года для решения сложных задач по созданию архитектурного макета с ограничениями на граф.