English Русский Español Deutsch 日本語
preview
Padrões de projeto no MQL5 (Parte 2): Padrões estruturais

Padrões de projeto no MQL5 (Parte 2): Padrões estruturais

MetaTrader 5Negociação | 29 abril 2024, 13:30
221 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Introdução

Apresento a vocês um novo artigo dedicado a um aspecto importante do desenvolvimento de software: os padrões de projeto. No artigo anterior, falamos sobre padrões criacionais. Se você não o leu, recomendo que comece por ele: Padrões de projeto no MQL5 (Parte I): Padrões criacionais. O artigo anterior é uma introdução ao tópico de padrões de projeto (design patterns) e fala, de modo geral, sobre a utilidade deles no desenvolvimento de software.

Quem deve estudar padrões de projeto? Todos que desejam desenvolver habilidades de programação e elevá-las a um novo patamar. Esses padrões fornecem um esboço pronto para resolver problemas específicos. Com eles, não é necessário reinventar a roda. Com o uso desses padrões você obtém soluções muito práticas e comprovadas.

Neste artigo, exploraremos os padrões de projeto estruturais. Veremos como eles podem ser úteis na escrita de programas e como ajudam a formar estruturas maiores. Enquanto isso, usaremos as classes existentes. E claro, o objetivo principal do artigo é mostrar como esses padrões podem ser usados na programação no MQL5, como desenvolver aplicativos de negociação práticos e como usá-los no terminal MetaTrader 5.

Os padrões estruturais que estudaremos nas próximas seções são:

Espero que o artigo seja útil e ajude você a desenvolver habilidades de programação. Lembro novamente que os padrões de projeto estão intimamente ligados à programação orientada a objetos, com a qual é muito importante estar familiarizado para entender esta série de artigos. Por isso, se você precisa de mais informações introdutórias sobre POO, recomendo que leia um dos meus artigos anteriores: "Programação Orientada a Objetos (POO) no MQL5".

Atenção! O conteúdo deste artigo é fornecido "como está", destinado apenas para fins educacionais e não é uma recomendação de negociação. O artigo não garante nenhum resultado. Tudo o que você aplica na prática com base neste artigo, você faz exclusivamente por sua própria conta e risco, o autor não garante nenhum resultado.


Padrões estruturais

Nesta parte, aprenderemos sobre os padrões estruturais de projeto, seus tipos e estruturas. Os padrões estruturais estão relacionados ao método de estruturação de classes e objetos que, por sua vez, servirão como componentes para criar estruturas maiores. Esses padrões são usados para compor a partir a partir de interfaces e implementações, baseando-se principalmente no conceito de herança. O conceito de herança implica que temos uma classe que possui ou combina as propriedades de suas classes pai. Esse padrão é particularmente útil quando é necessário preparar o trabalho conjunto de várias classes desenvolvidas de forma independente.

Tipos de padrões estruturais:

  • Adaptador (Adapter) — permite obter uma interface que o cliente espera, transformando a interface de uma classe.
  • Ponte (Bridge) — permite separar a abstração e sua implementação para que elas possam mudar independentemente.
  • Compositor (Composite) — combina objetos em uma estrutura em árvore para representar uma hierarquia do particular para o todo. Além disso, o Compositor permite que os clientes tratem objetos individuais e suas composições de maneira uniforme.
  • Decorador (Decorator) — pode ser usado para conectar dinamicamente comportamento adicional aos objetos existentes, bem como uma alternativa flexível à prática de criação de subclasses para expandir a funcionalidade.
  • Fachada (Facade) — fornece uma interface unificada para um conjunto de interfaces em um subsistema, definindo um único ponto de interação com o subsistema.
  • Peso mosca (Flyweight) — usado para reduzir custos ao trabalhar com um grande número de pequenos objetos.
  • Mediador (Proxy) — fornece um objeto que controla o acesso a outro objeto, interceptando todas as chamadas. 

Ao considerar esses padrões, tentaremos responder às seguintes perguntas para cada um:

  • O que faz o padrão?
  • Qual é o problema que o modelo resolve?
  • Como usá-lo no MQL5?


Adaptador (Adapter)

Começaremos a analisar os tipos de padrões estruturais começando com o Adaptador. A palavra-chave para entender esse modelo é adaptabilidade. Simplificando, se temos uma interface que pode ser usada em certas condições e, em seguida, essas condições são atualizadas, precisaremos fazer atualizações na interface para que o código possa se adaptar e funcionar de forma eficaz sob essas novas condições. É isso que o padrão faz: ele transforma a interface da nossa classe em outra, que o cliente pode usar. Assim, o padrão Adaptador permite que classes trabalhem juntas em casos de interfaces incompatíveis. O padrão também é chamado de wrapper (embrulhador), porque cria uma classe embrulhadora com a interface necessária.

O que faz o padrão?

O padrão pode ser usado quando a interface projetada não atende aos requisitos da interface em um domínio específico. Ele transforma a interface dessa classe em outra para permitir a cooperação das classes.

Os diagramas abaixo mostram a estrutura do padrão Adaptador:

Adapter1

Adapter1

Como visto nas figuras, graças ao suporte a múltipla herança, temos um adaptador de classe e um adaptador de objeto. Há também um alvo, que identifica a nova interface do domínio específico que o cliente usa. Além disso, há um cliente que interage com os objetos adaptados à interface alvo, um objeto adaptável, que representa a antiga interface existente que precisa ser adaptada, e um adaptador, que faz a interface do objeto adaptável compatível com a interface alvo.

Qual é o problema que o modelo resolve?

  • O uso de uma classe existente com uma interface que não corresponde à interface necessária.
  • Criar classes reutilizáveis que possam operar com classes não relacionadas, independentemente de essas classes terem interfaces compatíveis ou incompatíveis.
  • Adaptar a interface de uma classe pai quando é necessário usar muitos das suas subclasses existentes.

Como usá-lo no MQL5?

Vamos ver como podemos usar esse padrão (AdapterClass e ObjectClass) no MQL5. Então, vamos detalhar o código passo a passo:

Usamos a função de espaço de nomes e declaramos uma área (AdapterClass), onde definiremos funções, variáveis e classes.

namespace AdapterClass

Usamos a função interface para declarar Target. Isso define a funcionalidade específica que pode ser implementada mais tarde pela classe.

interface Target
  {
   void Request();
  };

Usamos a função Class para definir a interface da classe Adaptee, que estabelece a interface existente que precisa ser adaptada por meio de um membro público (SpecificRequest()).

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

 Exibimos uma mensagem quando a solicitação for executada pela interface da classe Adaptee

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

Declaramos a classe Adapter, que adapta a interface do objeto adaptável Adaptee às interfaces do alvo Target, herdadas do alvo e do objeto adaptável como herança múltipla.

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;
  }

Declaração da classe Client

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

Execução do cliente

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

A seguir, o código MQL5 completo do Adaptor é apresentado em um único bloco.

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();
  }
}

A seguir, é mostrado como usar o adaptador de objeto no MQL5:

Usamos a função namespace, e declaramos uma área onde definiremos funções, variáveis e classes AdapterObject.

namespace AdapterObject

Usamos interface para definir Target (objeto de interface alvo).

interface Target
  {
   void Request();
  };

Criamos a classe Adaptee para definir a interface existente que precisa ser adaptada.

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();
  }

Declaração da classe Client

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

Execução do cliente, quando os clientes iniciam operações na instância do adaptador

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

O código completo fica assim:

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;
  }
}


Ponte (Bridge)

Nesta parte, vamos conhecer outro padrão estrutural de design: Bridge (ponte). A ideia principal de usar este padrão é separar a abstração de suas implementações, para evitar quaisquer conflitos futuros que possam surgir no caso de atualizações ou mudanças em qualquer uma delas. Também é conhecido como Handle ou Body.

O que faz o padrão?

O padrão Bridge (ponte) é usado em casos onde há uma abstração que possui muitas possíveis implementações. Em vez de usar o herança comum, que sempre vincula a implementação à abstração, esse padrão pode ser usado para separar a abstração de suas implementações, para evitar problemas no caso de mudanças ou atualizações. Tal separação pode ajudar a criar um código limpo, que pode ser reutilizado, expandido e facilmente testado.

A estrutura do padrão "Ponte" (Bridge) é mostrada no diagrama abaixo:

Ponte (Bridge)

No diagrama da Ponte, mostrado acima, estão presentes os seguintes elementos:

  • Abstraction — abstração, define a interface da abstração e mantém uma referência ao objeto implementador.
  • RefinedAbstraction — expande a interface da abstração.
  • Implementor — implementação, identifica a interface das classes de implementação.
  • ConcreteImplementor — implementa a interface do desenvolvedor e identifica uma implementação específica dessa interface.

Qual é o problema que o modelo resolve?

O padrão Ponte pode ser usado quando precisamos:

  • Evitar uma ligação permanente entre a abstração e sua implementação (separá-las usando o padrão).
  • Combinar diferentes abstrações e implementações e expandir cada uma delas independentemente sem quaisquer conflitos.
  • Evitar impactos nos clientes quando a implementação da abstração é alterada.
  • Ocultar completamente a implementação da abstração dos clientes.

Como usá-lo no MQL5?

Nesta parte, aprenderemos como usar esse padrão no MQL5 e criar aplicativos mais eficientes do ponto de vista da codificação. Abaixo está mostrado como implementar a estrutura do padrão Ponte no código MQL5:

Primeiro, criamos uma área de declaração para definir variáveis, funções e classes do padrão.

namespace Bridge

Em seguida, criamos a interface Implementor aqui usamos a palavra-chave interface, permitindo definir a funcionalidade que será implementada pela classe.

interface Implementor
  {
   void OperationImp();
  };

No fragmento abaixo, criamos uma classe de abstração com membros públicos e protegidos e mantemos uma referência ao objeto implementador.

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();
  }

Depois, criamos a classe RefinedAbstraction, já que ela será o participante neste exemplo.

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

Criamos as classes ConcreteImplementorA e 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");
  }

Criamos a classe Client

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

Execução do cliente

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

Abaixo está o código para a estrutura do padrão Ponte completo em um único bloco.

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;
  }
}


Compositor (Composite)

Trata-se de outro tipo de padrão estrutural. Este padrão permite combinar objetos em uma estrutura de árvore e, assim, proporcionar um tratamento uniforme dos clientes de objetos individuais e composições.

O que faz o padrão?

O padrão Compositor (Composite) é usado quando precisamos compor objetos em estruturas arbóreas. Por conseguinte, a árvore é o elemento principal neste padrão. Neste caso, se temos um componente que é apresentado em forma de estrutura arbórea, neste componente do padrão temos dois elementos: Folha (Leaf), que neste caso realiza apenas operações, e o próprio Compositor (Composite), que executa um conjunto de operações como adicionar, remover e chamar um elemento filho.

Abaixo está o diagrama da estrutura do padrão de projeto Compositor:

Compositor (Composite)

Vamos considerar os elementos presentes no diagrama acima:

  • Component — componente; declara a interface para os objetos e implementa o comportamento padrão da interface para as classes, proporcionando acesso e gerenciamento dos componentes declarados para essa interface.
  • Leaf — folha; representa objetos folha na composição, e esta folha não tem elementos filhos, define o comportamento de objetos que podem ser considerados primitivos na composição.
  • Composite — compõe identifica o comportamento dos componentes com elementos filhos, mantém esses elementos filhos dos componentes e implementa as operações dos elementos filhos na interface dos componentes.
  • Client — por meio da interface do componente, o cliente manipula os objetos.

Qual é o problema que o modelo resolve?

O padrão Composite pode ser usado quando precisamos:

  • Representar objetos em uma hierarquia do particular para o todo.
  • Garantir o mesmo processamento de cliente para todos os objetos na composição.

Como usá-lo no MQL5?

Agora vamos ver como podemos usar o padrão Compositor no MQL5. Então, vamos detalhar o código passo a passo:

Criaremos uma área Composite, na qual declararemos todas as funções, variáveis e classes. A área é criada usando a palavra-chave namespace.

namespace Composite

Criaremos a classe Component com membros abertos e protegidos e acesso ao elemento pai do componente.

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) {}

A seguir, no fragmento, decidi mostrar um erro do usuário ao adicionar e remover da lista ou ao criar a classe 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;
  }

Criação da classe Composite como participante. Trabalho, adição, remoção de componentes e 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];
  }

Criação da classe Client como participante

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

Execução do cliente

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;
  }


Decorador (Decorator)

O padrão estrutural de design Decorator (Decorador) permite formar estruturas maiores para objetos criados ou existentes. Este padrão pode ser usado para adicionar funções, comportamentos ou capacidades adicionais a um objeto de maneira dinâmica durante a execução. Assim, o padrão é uma alternativa flexível à criação de subclasses. Este padrão também é conhecido como Wrapper (Embrulho).

O que faz o padrão?

Bem, o padrão permite adicionar responsabilidades a qualquer objeto individual, sem fazer isso em toda a classe. Para isso, usa-se um embrulho em vez de usar o método de criação de subclasses.

Abaixo está o diagrama da estrutura do padrão de projeto Decorador:

Decorador (Decorator)

No diagrama, pode-se ver que os seguintes elementos participam deste padrão:

  • Component (Componente) — define a interface dos objetos e que eles podem ter funções adicionadas dinamicamente.
  • ConcreteComponent (Componente Concreto) — define qual objeto pode ter responsabilidades adicionais impostas.
  • Decorator (Decorador) — permite manter uma referência ao objeto componente e define uma interface que corresponde à interface do componente.
  • ConcreteDecorator (Decorador Concreto) — responsável por adicionar responsabilidades ao componente.

Qual é o problema que o modelo resolve?

O padrão Decorador pode ser usado quando precisamos:

  • Adicionar responsabilidades de forma dinâmica e transparente a objetos individuais, sem afetar outros objetos.
  • Remover responsabilidades de objetos.
  • Quando o uso do método de criação de subclasses não é prático para estender comportamentos.

Como usá-lo no MQL5?

Para implementar o padrão Decorador no código MQL5 e depois usá-lo para escrever programas, executaremos os seguintes passos:

Novamente começamos criando uma área de declaração para nosso Decorador, dentro da qual declararemos tudo que precisamos.

namespace Decorator

Criaremos a classe Component com um membro aberto para definir a interface dos objetos.

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

Criaremos a classe Decorator como participante.

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

Criaremos a classe ConcreteComponent como participante

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

Criação do ConcreteDecoratorA e 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();
  }

Criamos a classe Client

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

Execução do cliente

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;
  }

Abaixo, compilei todo o código do padrão em um único bloco

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;
  }
}


Fachada (Facade)

É outro padrão estrutural que pode ser usado no desenvolvimento de software para criar outras estruturas maiores. Ele identifica uma interface de alto nível, tornando o uso das subsistemas mais suave e simples.

O que faz o padrão?

O uso do padrão Fachada pode ser visto como uma maneira de ocultar a complexidade de um subsistema do Cliente, pois fornece uma interface unificada para um conjunto de interfaces do subsistema. Assim, o cliente interage com essa interface unificada para obter o que ele solicita, e esta interface, por sua vez, interage com o subsistema para retornar o que o cliente solicitou.

Veja o diagrama abaixo, mostrando a estrutura de funcionamento do padrão de projeto Fachada:

Fachada (Facade)

Conforme visto no diagrama, a estrutura do padrão consiste nos seguintes elementos:

  • Fachada (Facade) — sabe quais subsistemas o cliente pode solicitar e delega as solicitações do cliente aos objetos apropriados do subsistema.
  • Classes de subsistemas — executam funções do subsistema, processam solicitações recebidas da Fachada, e não têm referências à Fachada.

Qual é o problema que o modelo resolve?

O padrão Fachada pode ser usado quando é necessário:

  • Simplificar a complexidade de um subsistema através do fornecimento de uma interface simples.
  • Separar o subsistema dos clientes e outros subsistemas para modificar as dependências existentes entre os clientes e as implementações das classes de abstração e, ao mesmo tempo, tornar o subsistema independente e portátil.
  • Definir pontos de entrada para cada nível do subsistema por meio de sua representação hierárquica.

Como usá-lo no MQL5?

Para implementar o padrão Fachada no código MQL5 e depois usá-lo para escrever programas, executaremos os seguintes passos:

Criaremos o espaço Facade para declarar tudo que precisamos.

namespace Facade

Declaramos as classes SubSystemA, SubSystemB e SubSystemC

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");
  }

Declaração da classe 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();
  }

Declaração da classe Client

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

Execução do cliente

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();
  }

Abaixo está todo o código em um único bloco:

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();
  }
}


Peso mosca (Flyweight)

O padrão estrutural Peso mosca (Flyweight) é usado para reduzir custos ao trabalhar com um grande número de pequenos objetos, nesse caso é utilizado o compartilhamento para dar suporte a eles.

O que faz o padrão?

O compartilhamento para suporte pode ser útil também do ponto de vista da memória, e é por isso que ele é originalmente chamado de Flyweight ("Peso mosca").

A estrutura do padrão Peso mosca é mostrada nos diagramas abaixo:

Flyweight1

Flyweight2

Vamos considerar os elementos presentes no diagrama acima:

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

Qual é o problema que o modelo resolve?

Este padrão pode ser usado quando:

  • Há um grande número de objetos sendo usados em um aplicativo.
  • Precisamos reduzir os custos elevados de armazenamento.
  • A maior parte do estado do objeto pode ser externa.
  • Se removermos o estado externo, muitos grupos de objetos podem ser substituídos por um número menor de objetos compartilhados.
  • A identidade do objeto não é tão importante para o aplicativo em termos de dependência.

Como usá-lo no MQL5?

Vamos agora escrever o código MQL5 deste padrão:

Começamos criando uma área de declaração para nosso padrão Flyweight, dentro da qual declararemos tudo que precisamos.

namespace Flyweight

Para isso, usamos a palavra-chave interface e declaramos Flyweight

interface Flyweight;

Criaremos a classe Pair com membros protegidos e abertos como participante

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;
  }

Criaremos a classe Reference e definiremos seu construtor e destruidor.

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;
  }

Declararemos a interface Flyweight para atuar sobre o estado externo

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

Declararemos a classe 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);
  }

Declararemos a classe 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);
  }

Declararemos a classe 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];
  }

Declaração da classe Client

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

Execução do cliente

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;
  }

Abaixo está o código completo em um único bloco:

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;
  }
}


Mediador (Proxy)

Chegamos ao último dos tipos de padrões de projeto estruturais, o Proxy (mediador). Este padrão tem muitos tipos em termos de representatividade. Em geral, pode-se dizer que o Proxy pode ser usado para representar uma alternativa ou um substituto para outro objeto para controle total em termos de acesso a esse objeto. Este padrão também é conhecido como Surrogate.

O que faz o padrão?

Este padrão fornece um substituto para controlar o acesso a um objeto.

Abaixo está o diagrama da estrutura do padrão de projeto Proxy:

Proxy (Mediador)

Vamos considerar os elementos presentes no diagrama acima:

  • Proxy.
  • Subject.
  • Real subject.

Qual é o problema que o modelo resolve?

O padrão Proxy é adequado para uso nas seguintes situações:
  • Se é necessário um representante local de um objeto em outro espaço de endereço, pode-se usar um proxy remoto, que o fornece.
  • Se é necessário um objeto muito exigente e caro, pode-se usar um proxy virtual, que cria esses objetos.
  • Se é necessário controlar o acesso ao objeto principal ou original, pode-se usar um proxy de proteção.
  • Se é necessária uma substituição para um simples ponteiro, pode-se usar uma referência inteligente.

Como usá-lo no MQL5?

Para implementar o padrão Proxy no código MQL5 e depois usá-lo para escrever programas, executaremos os seguintes passos:

Declararemos o espaço Proxy para declarar tudo que precisamos, em termos de variáveis, funções, classes, etc.

namespace Proxy

Declararemos a classe Subject como participante

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

Criação da classe RealSubject

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

Criação da classe Proxy como participante

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();
  }

Declaração da classe Client

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

Execução do cliente

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

Abaixo está o código completo em um único bloco:

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;
  }
}


Considerações finais

Assim, neste artigo, apresentamos uma introdução simples e informações sobre o tema dos padrões estruturais de design. Neste artigo, definimos os tipos de padrões estruturais e aprendemos a escrever código limpo, que pode ser reutilizado, expandido e testado. Para cada padrão, entendemos os principais pontos: a essência do padrão, seu propósito, estrutura e quais problemas de design ele resolve.

Exploramos os seguintes padrões de projeto estruturais:

  • Adaptador (Adapter)
  • Ponte (Bridge)
  • Compositor (Composite)
  • Decorador (Decorator)
  • Fachada (Facade)
  • Peso mosca (Flyweight)
  • Mediador (Proxy)

Como mencionado anteriormente na primeira parte, é útil para os desenvolvedores se iniciarem nos padrões de projeto, até porque eles economizam muito tempo e eliminam a necessidade de reinventar a roda. Para isso, eles usam soluções pré-definidas, comprovadas e práticas para problemas específicos. Com conhecimento suficiente em programação orientada a objetos, é possível começar a trabalhar bem com padrões de projeto.

Para aprender mais sobre o tema dos padrões, recomendo a leitura dos seguintes materiais:

  • 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

Espero que o artigo tenha sido útil para você e que você tenha encontrado algo novo no campo da programação. Talvez esses conhecimentos ajudem você a criar programas mais eficientes no MQL5. Se você gostou do artigo, leia também outros materiais. Nos links abaixo, você pode explorar outros artigos meus. Em particular, você pode encontrar uma série de artigos sobre a criação de sistemas de negociação baseados nos indicadores técnicos mais populares, como RSI, MACD, Bandas de Bollinger, Médias Móveis, Estocásticos e outros. Espero que sejam úteis para alguém e ajudem a elevar o desenvolvimento de software e a negociação para um novo nível.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13724

Arquivos anexados |
Adapter_Class.mqh (1.07 KB)
Adapter_Object.mqh (0.72 KB)
Bridge.mqh (3.38 KB)
Composite.mqh (3.09 KB)
Decorator.mqh (3.53 KB)
Facade.mqh (3.43 KB)
Flyweight.mqh (3.47 KB)
Proxy.mqh (1.79 KB)
Criação de um Expert Advisor simples em várias moedas usando MQL5 (Parte 4): Média móvel triangular — Sinais do indicador Criação de um Expert Advisor simples em várias moedas usando MQL5 (Parte 4): Média móvel triangular — Sinais do indicador
Neste artigo, por EA multimoeda, entendemos um robô investidor, ou um robô de negociação, que pode negociar (abrir/fechar ordens, gerenciar ordens, por exemplo, do tipo trailing stop-loss e trailing profit) mais de um par de moedas em um gráfico. Desta vez, usaremos apenas um indicador, em particular a média móvel triangular em um ou mais timeframes, ou escalas de tempo.
Validação cruzada combinatoriamente simétrica no MQL5 Validação cruzada combinatoriamente simétrica no MQL5
Neste artigo veremos como implementar a verificação cruzada combinatoriamente simétrica no MQL5 puro para medir o grau de ajuste após a otimização de uma estratégia usando o algoritmo completo e lento do testador de estratégias.
Algoritmos de otimização populacionais: otimização de dinâmica espiral (Spiral Dynamics Optimization, SDO) Algoritmos de otimização populacionais: otimização de dinâmica espiral (Spiral Dynamics Optimization, SDO)
Neste artigo examinaremos a otimização de dinâmica espiral (SDO), um algoritmo de otimização baseado nos padrões de trajetórias espirais presentes na natureza, como nas conchas de moluscos. O algoritmo proposto pelos autores foi completamente repensado e modificado por mim, e o artigo discutirá por que essas mudanças foram necessárias.
Aprendendo MQL5 do iniciante ao profissional (Parte I): Comecemos a programar Aprendendo MQL5 do iniciante ao profissional (Parte I): Comecemos a programar
Este artigo é uma introdução a uma série completa de artigos sobre programação. Aqui supomos que o leitor nunca teve contato com programação antes. Por isso, começo pelo básico, com nível de conhecimento de programação: iniciante absoluto.