English Русский 中文 Español Deutsch 日本語
preview
Padrões de projeto no MQL5 (Parte I): Padrões criacionais (creational patterns)

Padrões de projeto no MQL5 (Parte I): Padrões criacionais (creational patterns)

MetaTrader 5Negociação | 24 abril 2024, 11:34
81 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Introdução

Fundamentalmente, a programação é baseada na resolução de problemas, e os mesmos problemas podem ocorrer em diferentes partes do mesmo programa ou até em diferentes aplicativos. Imagine que, ao enfrentar o mesmo problema várias vezes, repetimos os mesmos passos e dedicamos tempo novamente e novamente para resolvê-lo. De fato, com essa abordagem, estamos constantemente reinventando a roda. Este método não é apenas inútil, senão que também é prejudicial, pois consome muito tempo e esforço. Isso levanta a questão: existe uma maneira de economizar esse tempo ineficiente e nossos esforços?

A resposta é sim, existem maneiras, ou melhor, padrões, que podem ser usados para resolver problemas específicos. Estes são os padrões de projeto (design patterns). Os padrões de projeto apoiam o conceito de OOP DRY ("não se repita"), o que significa que não é necessário reinventar a roda todas as vezes. Se há um problema e um padrão que pode ser usado para resolvê-lo eficientemente, simplesmente usamos esse padrão e economizamos tempo e esforço.

Neste artigo, analisaremos os padrões de projeto e seus tipos, e tentaremos criar um guia prático sobre como usá-los ao escrever programas no MQL5.

Neste artigo, abordaremos os seguintes tópicos:

Os padrões de projeto (design patterns) são um tópico muito importante para todos os programadores que desejam aumentar sua produtividade e resultados. Vamos tentar revisar os conceitos deste interessante tópico da maneira mais simples possível, para que tudo seja compreensível para os iniciantes. Também vamos considerar exemplos simples para usar esses padrões no MQL5. Isso será, de certa forma, um guia prático.

Vamos tentar cobrir cada tipo e considerar todos os detalhes necessários, cuja falta pode dificultar a compreensão. Espero que este artigo seja informativo para você e que você aprenda algo novo.

Observe que para entender o material, é necessário ter conhecimento sobre design orientado a objetos. Se você não está familiarizado com OOP, recomendo ler o artigo "Programação Orientada a Objetos (OOP) 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, e o autor não garante nenhum resultado.


Definição de padrões de projeto

Nesta parte, vamos nos iniciar nos padrões de projeto. Esses são padrões que podem ser usados para resolver problemas específicos e recorrentes no desenvolvimento de software. Cada padrão é projetado para um problema específico de orientação a objetos. Esses padrões podem ser facilmente implementados nas principais linguagens orientadas a objetos. Ao falar sobre um padrão de projeto, geralmente são mencionados quatro elementos importantes:

  • Nome — o nome do padrão para resolver um problema específico e descrito.
  • Problema — um problema descrito, recorrente e específico que podemos encontrar.
  • Solução — uma solução descrita para um problema específico.
  • Consequências — os resultados da aplicação do padrão para resolver o problema.

Dependendo do uso, podemos distinguir três tipos de padrões:

  • Criacionais — esses padrões destacam o processo de criação de um sistema que deixa de depender do método de formação e composição do objeto.
  • Estruturais — responsáveis pelo uso de objetos criados para formar estruturas maiores.
  • Comportamentais — definem algoritmos e métodos de implementação da interação entre objetos.

Padrões criacionais (creational patterns)

Neste artigo, estamos falando sobre padrões criacionais de projeto e como eles podem ajudar na programação. Assim, um padrão criacional permite criar um sistema independente que deixa de depender do método de formação e composição do objeto. O principal objetivo desses padrões é ajudar o programador a criar aplicativos de forma prática, com a possibilidade de reutilização de código e sua expansão. Eles também oferecem a capacidade de testar programas de forma eficiente e escrever código limpo.

As classes de um padrão criacional usam o conceito de herança, ou seja, podem herdar propriedades e métodos de outras classes. Isso permite alterar o comportamento da classe, transformando-a em uma instância (objeto) que atende a determinados requisitos. Um objeto de um padrão criacional delega a tarefa de criar uma nova instância (instanciação) a outro objeto. Quando o programa se concentra na composição de objetos, em vez de na herança de classes, os padrões criacionais se tornam mais importantes.

Pode-se dizer que esses padrões têm dois aspetos recorrentes:

  • Eles usam o conceito de encapsulamento para obter conhecimento sobre classes específicas que o sistema pode usar.
  • Eles tornam o método de criação de instâncias de classes e sua união oculto.

Os padrões criacionais oferecem flexibilidade em termos do que é criado, por quem, como e quando.

Eles também permitem abstrair o processo de instanciação, pois permitem criar objetos sem repetir a mesma implementação. Isso torna o código mais flexível e simples.

Tipos de padrões criacionais:

  • Fábrica abstrata — apresenta uma interface para criar famílias de objetos sem mencionar suas classes.
  • Construtor — interface que permite criar objetos complexos. Ele separa a construção do objeto de sua representação, de modo que o mesmo processo de construção pode resultar em diferentes representações.
  • Método de fábrica — fornece uma interface para subclasses criarem objetos e permite que essas subclasses decidam qual instância de classe criar.
  • Protótipo — define os tipos de objetos a serem criados usando uma instância protótipo e cria novos objetos copiando esse protótipo.
  • Singleton — garante que em um aplicativo de thread único haja uma única instância de alguma classe e fornece um ponto de acesso global a essa instância.

A seguir, veremos como esses padrões podem ser usados na programação MQL5. Para isso, responderemos a três perguntas para cada padrão:

  • O que faz o padrão?
  • Que problema de design ele resolve?
  • Como usá-lo no MQL5?


Fábrica abstrata

A fábrica abstrata é um dos padrões criacionais. Este padrão consiste em fábricas (factory) e produtos (product) e é usado para criar famílias de objetos relacionados sem a criação direta de instâncias de classe. Pode ser usado quando a quantidade e os tipos gerais de objetos de produto são constantes, mas variam em famílias de produtos individuais.

O que faz o padrão?

Este padrão fornece uma interface para criar famílias de objetos sem especificar suas classes, e esses objetos criados podem ser relacionados ou independentes. Também é conhecido como ‘kit’ ("conjunto de ferramentas"). Abaixo está um diagrama que mostra como este padrão funciona e o que ele faz.

Fábrica Abstrata

Como você pode ver no diagrama, quando temos muitos produtos semelhantes elaborados por diferentes fabricantes ou fábricas, é difícil fazer alterações futuras se não usarmos o padrão "fábrica abstrata". Mas com esse padrão, o processo se torna muito mais fácil.

Neste padrão, definimos uma classe de fábrica abstrata que declara uma interface para operações que criam produtos abstratos. Também temos uma classe abstrata para cada fábrica com subclasses para dois produtos, e cada fábrica retornará seus produtos dependendo de qual produto é chamado pelo cliente.

Que problema de design ele resolve?

Então, podemos usar esse padrão quando:

  • Precisamos de um sistema independente.
  • Precisamos de um sistema configurado com um entre muitos conjuntos de produtos.
  • Precisamos usar uma família de produtos relacionados juntos, de acordo com o design e garantir que essa restrição seja observada.
  • Precisamos revelar apenas as interfaces da classe fornecida, não sua implementação.

Exemplos de uso de uma fábrica abstrata:

  • Isolamento de classes específicas, já que o padrão permite controlar as classes de objetos criados. Isso é alcançado através da encapsulação de responsabilidades e do processo de criação de objetos, isolando o cliente das classes de implementação, através das interfaces abstratas clientes podem manipular instâncias, e dentro da implementação de uma fábrica específica, os nomes das classes de produto são isolados e não aparecem no código do cliente.
  • Isso facilita a substituição de famílias de produtos.

Como usá-lo no MQL5?

Vamos ver como podemos implementar a estrutura de uma fábrica abstrata na forma de um arquivo include. Neste artigo, exploraremos uma estrutura, mas você pode escolher sua própria implementação de acordo com suas necessidades.

Bem, vamos escrever o código da estrutura passo a passo:

Utilizamos a palavra-chave namespace, declaramos a função AbstractFactory para listar todas as funções necessárias dentro dela.

namespace AbstractFactory

Usando a palavra-chave interface, declaramos a função AbstractProductA.

interface AbstractProductA
  {
  };

Em seguida, novamente com a palavra-chave interface, declaramos a função AbstractProductB com a variável void Interact no corpo da função.

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

Declaramos uma interface para operações de criação de produtos abstratos.

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

Construímos os produtos A1 e A2 — para isso, definimos o objeto do produto que precisa ser criado usando uma fábrica específica e suas ferramentas, através da interface abstrata do produto.

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

Construímos os produtos concretos B1 e B2, e então permitimos a interação com o produto abstrato A.

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

Declaramos as fábricas concretas Factory1 e Factory2, que criam e retornam os produtos A1, A2, B1, B2.

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

Declaramos a classe FactoryClient e usamos as interfaces declaradas pela fábrica abstrata e pelo produto abstrato.

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

Definimos a classe Client e iniciamos o padrão. 

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

Aqui está o bloco de código que obtemos:

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


Construtor

O padrão criacional "Construtor" é usado quando é necessário criar objetos complexos. Ele separa a construção do objeto de sua representação. Como resultado do mesmo processo de construção, podem ser obtidas diferentes representações.

O que faz o padrão?

O diagrama abaixo mostra o princípio de funcionamento deste padrão:

Construtor

Como você pode ver, dependendo da estrutura, temos um Builder, que define uma interface para criar partes do objeto do produto, um Director, que cria o objeto usando a interface do Builder, e um ConcreteBuilder, que pode ser usado para executar as seguintes ações:

  • Construção e montagem das partes do produto por meio da implementação da interface Builder.
  • Definição da representação e seu monitoramento.
  • Obtenção do produto por meio da interface fornecida.

O resultado final é um produto que representa um objeto complexo que está sendo construído.

Que problema de design ele resolve?

Este padrão pode ser usado quando precisamos do seguinte:

  • Um algoritmo independente para criar um objeto complexo a partir das suas partes constituintes e de sua montagem.
  • Consideração de diferentes representações do objeto durante a construção.

O que ele permite fazer

  • Baseando-se na representação interna do produto, o padrão Builder permite modificá-las.
  • A encapsulação do método de construção e representação do objeto complexo ajuda a melhorar a modularidade, o que significa que o código se torna isolado a nível de construção e de representação.
  • Temos um controle mais preciso sobre o processo de fabricação dos produtos.

Como usá-lo no MQL5?

Agora vamos considerar a estrutura de implementação do padrão "Construtor".

Com a função namespace, declaramos a função Builder.

namespace Builder

No corpo da função, criamos a classe Product.

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

Adicionamos detalhes

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

Mostramos todos os detalhes do produto

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

Criamos uma interface abstrata Builder para criar as partes do produto A, B e C.

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

Criamos a classe Director, que constrói o objeto usando a interface do Builder com a função da classe.

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

Criamos o Builder e o obtemos no Director

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

O Director começa a criar as partes dos produtos A, B e C.

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

Criamos a classe ConcreteBuilder com três membros públicos para as partes do produto e um membro protegido para o produto.

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

O Construtor adiciona os detalhes A, B e C ao produto e, em seguida, retornamos o produto usando as seguintes funções.

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

Criamos a classe Client com dois membros públicos para os construtores Output e Run e iniciamos o cliente. 

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

Abaixo está o código para a estrutura do Builder inteiramente em um bloco.

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


Método de fábrica

O padrão criacional método de fábrica define uma interface para a criação de um objeto e permite que as subclasses escolham a classe para criar a instância, além de permitir que a classe adie a criação da instância para as subclasses. Este padrão também é conhecido como construtor virtual (virtual constructor).

O que faz o padrão?

Estrutura do método de fábrica:

Método de Fábrica

Bem, nesta estrutura temos os seguintes elementos:

  • Product (produto) — define a interface dos objetos criados pelo método de fábrica.
  • ConcreteProduct (produto concreto) — responsável pela implementação da interface do produto.
  • Creator (criador) — retorna um objeto do produto após declarar o método de fábrica, pode fornecer uma implementação padrão para o método de fábrica que retorna um objeto ConcreteProduct padrão, e pode criar um objeto do produto chamando o método de fábrica.
  • ConcreteCreator (criador concreto) — retorna uma instância do produto concreto ConcreteProduct, substituindo o método de fábrica.

Que problema de design ele resolve?

O padrão método de fábrica pode ser utilizado quando:

  • Temos uma classe que não pode prever qual classe de objetos precisa criar.
  • A classe deseja especificar os objetos criados por suas subclasses.
  • Um de vários subclasses auxiliares é delegado pelas classes como responsável, e precisamos saber qual subclasse auxiliar é o delegado.

Como usá-lo no MQL5?

A seguir, vamos considerar como implementar o padrão método de fábrica na forma de um arquivo de inclusão em MQL5.

Usamos a palavra-chave namespace, para declararmos FactoryMethod

namespace FactoryMethod

Criamos a interface do objeto Product

interface Product
  {
  };

Criamos a classe ConcreteProduct e implementamos a interface do produto

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

Criamos a classe Creator, retornamos um objeto do tipo produto, implementamos o retorno de um produto concreto, criamos um objeto do produto

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

Iniciamos o método de fábrica, criamos e retornamos um novo produto concreto

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

Criamos a classe Client com dois membros públicos Output e Run.

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

Iniciamos a classe Client para solicitar a criação do criador Creator, retornamos o produto, iniciamos a operação do criador.

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

Abaixo está o código completo em um bloco, para demonstrar, assim, a estrutura do método de fábrica.

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


Protótipo

O protótipo é outro padrão criacional que não apenas define os tipos de objetos a serem criados usando uma instância-protótipo, senão que também cria novos objetos por meio da cópia deste protótipo.

O que faz o padrão?

Abaixo está a estrutura do padrão de projeto protótipo:

Protótipo

Nesta estrutura, temos os seguintes elementos:

  • Protótipo (Prototype) — cria uma interface que pode ser clonada.
  • Protótipo Concreto (ConcretePrototype) — clona a si mesmo, implementando essa operação.
  • Cliente (Client) — solicita ao protótipo que se clone para criar um novo objeto.

Que problema de design ele resolve?

O padrão .protótipo pode ser utilizado quando:

  • As classes das quais precisamos criar instâncias são especificadas durante a execução.
  • É necessário evitar a construção de uma hierarquia de classes de fábrica que pode ser paralela à hierarquia de classes de produtos.
  • Existem instâncias de classes que podem ter uma das várias combinações diferentes de estados.

O que o uso do padrão protótipo nos permite:

  • Facilita a adição ou remoção de produtos durante a execução, pois o cliente pode instalar e remover protótipos.
  • Permite especificar novos objetos definindo os valores das variáveis do objeto.
  • Permite definir novos objetos através da modificação de sua estrutura.
  • Em vez de criar um novo objeto, o protótipo clona um protótipo existente, o que significa uma redução de subclasses.
  • Permite que o aplicativo configure dinamicamente as classes.

Como usá-lo no MQL5?

A seguir está o método para escrever o código da estrutura do padrão protótipo.

Usamos a palavra-chave namespace, declaramos a função Prototype.

namespace Prototype

Criamos uma classe ou interface Prototype para clonar a si mesma.

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

Criamos os protótipos concretos ConcretePrototype1 e ConcretePrototype2 — é aqui que a operação de clonagem é implementada.

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

Criamos a classe Client para criar um novo objeto clonando o protótipo para si mesmo.

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

Iniciamos o cliente, que solicita ao protótipo que se clone.

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

O código completo da estrutura do padrão protótipo em um único bloco:

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


Singleton

O principal objetivo deste padrão é garantir que exista apenas uma instância de uma classe e que o acesso a ela seja feito através de um ponto global.

O que faz o padrão?

A estrutura do padrão singleton é mostrada no diagrama abaixo:

Singleton

Neste diagrama, o padrão define o funcionamento da instância, que permite aos clientes acessarem sua instância. O modelo singleton também pode ser responsável por criar sua própria instância única.

Que problema de design ele resolve?

O padrão de projeto singleton pode ser utilizado quando:

  • É essencial que uma classe tenha apenas uma instância e que ela esteja acessível aos clientes por meio de um ponto de acesso conhecido.
  • É necessária uma única instância expandida através da criação de uma subclasse, que pode ser usada sem alterações.

Abaixo estão alguns exemplos de resultados da aplicação do padrão singleton:

  • Ao encapsular uma classe em sua única instância nesse padrão, controlamos o acesso a essa instância.
  • A aplicação do padrão singleton será mais flexível do que usar uma operação de classe.

Como usá-lo no MQL5?

De acordo com o esquema já conhecido, vamos desenvolver o código do padrão singleton no MQL5.

Usamos a palavra-chave namespace, declaramos a função singleton.

namespace Singleton

Criamos a classe singleton

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

Criamos o objeto singleton

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

Iniciamos a operação singleton e configuramos seus dados

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

Lemos e obtemos dados do singleton

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

Obtemos ou retornamos uma instância exclusiva

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

Criamos a classe Client

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

O cliente Client obtém acesso ao singleton através da instância

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

Abaixo está o código completo do padrão singleton em um bloco:

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


Considerações finais

Em conclusão, neste artigo fornecemos informações básicas sobre o tópico padrões de projeto. Exploramos o tipo padrões criacionais, que permitem criar objetos que podem ser usados repetidamente, expandidos e testados. Em outras palavras, são padrões que ajudam a escrever código limpo.

Revisamos os seguintes padrões criacionais:

  • Fábrica abstrata
  • Construtor
  • Método de fábrica
  • Protótipo
  • Singleton

Aqui, aprendemos a definição de padrões de projeto, vimos sua utilidade ao serem usados na programação de código orientado a objetos, examinamos as estruturas dos padrões, bem como os tipos de problemas que eles resolvem.

O tema dos padrões de projeto é bastante popular no desenvolvimento de software. Um bom entendimento e uso desses conceitos ajudarão na escrita de programas e na solução de problemas que podem surgir em seu código. Recomendo que você leia e aprenda mais sobre o assunto para poder resolver problemas frequentes sem precisar reinventar a roda todas as vezes.

O que mais você pode ler sobre este tópico:

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

Espero que este artigo tenha sido útil para você e que você tenha aprendido algo novo sobre padrões de projeto. Também espero que este artigo o incentive a aprender mais sobre o assunto. Lembre-se novamente que este tema está intimamente relacionado com a programação orientada a objetos, sobre a qual falamos no artigo "Programação Orientada a Objetos (OOP) no MQL5". Se você achou o artigo útil, considere ler meus artigos anteriores. Neles, descrevo a criação de sistemas de negociação baseados em vários indicadores técnicos populares, bem como outras coisas interessantes no MQL5.

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

Arquivos anexados |
Builder.mqh (3 KB)
Factory_Method.mqh (1.51 KB)
Prototype.mqh (3.78 KB)
Singleton.mqh (2.01 KB)
Criando um Expert Advisor simples multimoeda usando MQL5 (Parte 3): Prefixos/sufixos de símbolos e sessão de negociação Criando um Expert Advisor simples multimoeda usando MQL5 (Parte 3): Prefixos/sufixos de símbolos e sessão de negociação
Recebi comentários de vários colegas traders sobre como usar o Expert Advisor multimoedas que estou analisando com corretoras que usam prefixos e/ou sufixos com nomes de símbolos, bem como sobre como implementar fusos horários de negociação ou sessões de negociação no Expert Advisor.
Algoritmos de otimização populacionais: algoritmo de gotas de água inteligentes (Intelligent Water Drops, IWD) Algoritmos de otimização populacionais: algoritmo de gotas de água inteligentes (Intelligent Water Drops, IWD)
Neste artigo é analisado um algoritmo interessante chamado de gotas de água inteligentes (IWD), inspirado na natureza inanimada, que simula o processo de formação do leito de um rio. As ideias desse algoritmo permitiram melhorar significativamente o líder anterior da classificação, o SDS, e o novo líder (SDSm modificado), como de costume, pode ser encontrado no arquivo do artigo.
Redes neurais de maneira fácil (Parte 63): pré-treinamento do transformador de decisões não supervisionado (PDT) Redes neurais de maneira fácil (Parte 63): pré-treinamento do transformador de decisões não supervisionado (PDT)
Continuamos nossa análise, desta vez, explorando a família de transformadores de decisão. Em trabalhos anteriores, já observamos que o treinamento do transformador subjacente à arquitetura desses métodos é bastante desafiador e requer uma grande quantidade de dados de treinamento rotulados. Neste artigo, consideramos um algoritmo para usar trajetórias não rotuladas com o objetivo de pré-treinar modelos.
Quantificação no aprendizado de máquina (Parte 2): Pré-processamento de dados, seleção de tabelas, treinamento do modelo CatBoost Quantificação no aprendizado de máquina (Parte 2): Pré-processamento de dados, seleção de tabelas, treinamento do modelo CatBoost
Este artigo trata da aplicação prática da quantização na construção de modelos baseados em árvores. São examinados métodos para selecionar tabelas quantizadas e para o pré-processamento de dados. O material será apresentado em linguagem acessível, sem fórmulas matemáticas complexas.