
Padrões de projeto no MQL5 (Parte 2): Padrões estruturais
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:
- Padrões estruturais
- Adaptador (Adapter)
- Ponte (Bridge)
- Compositor (Composite)
- Decorador (Decorator)
- Fachada (Facade)
- Peso mosca (Flyweight)
- Mediador (Proxy)
- Considerações finais
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".
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:
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:
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:
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:
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:
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:
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:
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





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso