Шаблоны проектирования в MQL5 (Часть 2): Структурные шаблоны
Введение
Представляю вашему вниманию новую статью, посвященную важному аспекту разработки программного обеспечения — шаблонам проектирования. В предыдущей статье мы говорили о порождающих шаблонах. Если вы ее не читали, рекомендую начать именно с нее: Шаблоны проектирования в MQL5 (Часть I): Порождающие шаблоны. Предыдущая статья представляет собой введение в тему дизайн-паттернов и рассказывает в целом, насколько они полезны при разработке программного обеспечения.
Кому стоит изучать шаблоны проектирования? Всем, кто хочет развить навыки программирования и вывести их на новый уровень. Эти паттерны дают готовый план для решения конкретных проблем. С ними не нужно изобретать велосипед. Вы используете эти паттерны и получаете очень практичные и тестируемые решения.
В этой статье мы изучим структурные шаблоны проектирования. Посмотрим, чем они могут быть полезны при написании программ, как они помогают формировать более крупные структуры. При этом будем использовать имеющиеся классы. Ну и конечно, основная цель статьи — показать, как можно использовать такие шаблоны при программировании на MQL5, как с помощью них разрабатывать эффективное приложения для торговли и как их использовать в терминале MetaTrader 5.
Паттерны структурного типа изучим в следующих разделах:
- Структурные шаблоны
- Адаптер (Adapter)
- Мост (Bridge)
- Компоновщик (Composite)
- Декоратор (Decorator)
- Фасад (Facade)
- Приспособленец (Flyweight)
- Заместитель (Proxy)
- Заключение
Надеюсь, статья окажется полезной и поможет вам в развитии навыков разработки и программирования. Снова напомню, что тема дизайн-паттернов тесно связана с объектно-ориентированным программированием, с которым очень желательно быть знакомым для понимания этой серии статей. Поэтому, если вам нужно больше вводных данных об ООП, рекомендую прочитать одну из моих более ранних статей, в которой рассказывается об этом: "Объектно-ориентированное программирование (ООП) в MQL5".
Структурные шаблоны
В этой части мы познакомимся со структурными шаблонами проектирования, их типами и структурами. Структурные паттерны связаны с методом структурирования классов и объектов, которые будут служить компонентами для создания более крупных структур. Эти паттерны используются для составления композиций из интерфейсов и реализаций, в основе своей используя концепцию наследования. Концепция наследования означает, что у нас будет класс, который имеет или сочетает в себе свойства своих родительских классов. Особенно полезен такой шаблон, когда нужно организовать совместную работу нескольких независимо разработанных классов.
Типы структурных шаблонов:
- Адаптер (Adapter) — позволяет получить интерфейс, который ожидает клиентами, путем преобразования интерфейса класса.
- Мост (Bridge) — позволяет разделять абстракцию и ее реализацию, чтобы они могли изменяться независимо.
- Компоновщик (Composite) — объединяет объекты в древовидную структуру, чтобы представить иерархию от частного к целому. Кроме того, компоновщик позволяет клиентам одинаково обрабатывать отдельные объекты и их композиции.
- Декоратор (Decorator) — его можно использовать для динамического подключения дополнительного поведения к текущим объектам, а также в качестве гибкой альтернативы практике создания подклассов с целью расширения функциональности.
- Фасад (Facade) — позволяет получить унифицированный интерфейс для набора интерфейсов в подсистеме через определение одной точки взаимодействия с подсистемой.
- Приспособленец (Flyweight) — используется для уменьшения затрат при работе с большим количеством мелких объектов.
- Заместитель (Proxy) — предоставляет объект, который контролирует доступ к другому объекту, перехватывая все вызовы.
При рассмотрении этих паттернов, постараемся по каждому ответить на следующие вопросы:
- Что делает шаблон?
- Какую проблему решает шаблон?
- Как его использовать в MQL5?
Адаптер (Adapter)
Разбираться с типами шаблонов структурного проектирования с самого первого — адаптера. Ключевое слово для понимания этой модели — адаптивность. Проще говоря, если у нас есть интерфейс, который можно использовать в определенных условиях, а затем в этих условиях происходят обновления, нужно вносить обновления в интерфейс, чтобы код мог адаптироваться и эффективно работать в этих новых условиях. Именно это и делает шаблон — он преобразует интерфейс нашего класса в другой, который клиент может использовать. Таким образом, шаблон адаптера позволяет классам работать вместе в случаях несовместимых интерфейсов. Паттерн также называют оболочкой (Wrapper), потому что он создает класс-оболочку с требуемым интерфейсом.
Что делает шаблон?
Шаблон можно использовать, когда спроектированный интерфейс не соответствует требованиям приложения к интерфейсу для конкретной предметной области. Он преобразует интерфейс этого класса в другой для обеспечения совместной работы классов.
На схемах ниже представлена структура шаблона Адаптер:
Как видно на схемах, благодаря поддержки множественного наследования, у нас есть адаптер класса и адаптер объекта. Также есть цель, которая идентифицирует конкретный для предметной области новый интерфейс, который использует клиент. Кроме того, есть клиент, который участвует с объектами, адаптируемыми к целевому интерфейсу, адаптируемый объект, который означает существующий старый интерфейс, который и нужно сделать адаптируемым, и адаптер, который делает интерфейс адаптируемого объекта адаптируемым к целевому интерфейсу.
Какую проблему решает шаблон?
- Использование существующего класса с интерфейсом, который не соответствует требуемому интерфейсу.
- Создание повторно используемых классов, которые могут работать с несвязанными классами, независимо от того, имеют ли эти классы совместимые или несовместимые интерфейсы.
- Адаптация интерфейса родительского класса, когда нужно использовать множество существующих подклассов.
Как его использовать в 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) показана на схеме ниже:
На схеме Моста, показанной выше, есть следующие элементы:
- 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, который выполняет набор операций, таких как добавление, удаление и вызов дочернего элемента.
Ниже приведена схема структуры шаблона проектирования "Компоновщик":
Рассмотрим элементы, присутствующие на схеме выше:
- 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 (обертка).
Что делает шаблон?
Итак, шаблон позволяет добавлять обязанности к любому отдельному объекту, не делая этого во всем классе. Для этого используется обертка вместо использования метода создания подклассов.
Ниже приведена схема структуры шаблона проектирования "Декоратор":
На схеме видно, что в данном шаблоне участвуют следующие элементы:
- 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 (Фасад) — знает, какая подсистема может запрашивать, и делегирует запросы клиента подходящим объектам подсистемы.
- Классы подсистемы — выполняют функции подсистемы, при получении запроса от Фасада обрабатывают его, не имеют ссылок на Фасад.
Какую проблему решает шаблон?
Шаблон Фасад можно использовать, когда нужно:
- Упростить сложность подсистемы за счет предоставления простого интерфейса.
- Отделить подсистему от клиентов и других подсистем для изменения существующих зависимостей между клиентами и реализациями классов абстракции и при это сделать подсистему независимой и переносимой.
- Определить точки входа на каждый уровень подсистемы путем их иерархического представления.
Как его использовать в 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) показана на схеме ниже:
Рассмотрим элементы, присутствующие на схеме выше:
- 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.
- 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
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования