Español
preview
Desenvolvendo um agente de Aprendizado por Reforço em MQL5 com Integração RestAPI (Parte 4): Organizando Funções em Classes no MQL5

Desenvolvendo um agente de Aprendizado por Reforço em MQL5 com Integração RestAPI (Parte 4): Organizando Funções em Classes no MQL5

MetaTrader 5Exemplos | 17 janeiro 2024, 12:52
180 0
Jonathan Pereira
Jonathan Pereira

Introdução

Bem-vindos à quarta parte da nossa jornada explorando como criar um agente de Aprendizado por Reforço em MQL5 com integração RestAPI. Até agora, abordamos coisas importantes, como usar RestAPIs em MQL5, criar funções MQL5 para interagir com a API REST do jogo da velha e fazer jogadas automáticas e scripts de teste. Isso nos deu uma base sólida e nos ajudou a entender como MQL5 se conecta com coisas de fora.

Neste artigo, vamos dar um passo importante e organizar nossas funções em classes dentro do MQL5. Vamos usar a programação orientada a objetos, que é uma maneira de escrever código que ajuda a manter as coisas organizadas e fáceis de entender. É importante porque torna mais fácil consertar e melhorar o código. Um código bem organizado e modular também pode ser usado em diferentes partes do projeto ou até em projetos futuros.

No restante do artigo, vamos explicar como reestruturar nossas funções MQL5 existentes em classes. Mostraremos como isso pode tornar o código mais legível e eficiente. Vamos dar exemplos práticos de como fazer isso e mostrar como isso pode tornar o código mais fácil de manter e melhorar.

A programação orientada a objetos (POO) é uma maneira poderosa de desenvolver software. No MQL5, usar classes é um grande avanço em relação à maneira procedural de escrever código. Nesta parte, vamos explorar como isso pode melhorar a qualidade do nosso projeto. Vamos olhar para quatro coisas importantes:

  1. Encapsulamento e Modularidade: Classes ajudam a organizar funções relacionadas e variáveis em um único lugar, tornando mais fácil a manutenção e reduzindo erros.

  2. Reutilização de Código: Uma vez que você escreve uma classe, pode usá-la em muitos lugares diferentes, economizando tempo e mantendo o código consistente.

  3. Facilidade de Manutenção e Melhoria: Com funções organizadas em classes, é mais fácil encontrar e corrigir bugs ou fazer melhorias. A estrutura clara torna o código mais acessível.

  4. Abstração e Flexibilidade: Classes facilitam a abstração, escondendo a complexidade e expondo apenas o que é necessário. Isso torna o código mais intuitivo e flexível.

Vamos mostrar que reorganizar nossas funções em classes no MQL5 não é apenas para deixar o código mais bonito. É uma mudança importante que torna o código mais eficiente, fácil de entender e de manter. Vamos explicar como transformar funções isoladas em métodos de classes bem definidas, trazendo benefícios imediatos e a longo prazo. Isso não só aprimora nosso projeto atual, mas também nos ajuda a criar uma base sólida para futuros projetos em MQL5.


Estado Atual do Código 

No estado atual, nosso código, ele está composto por uma série de funções destinadas a gerenciar requisições HTTP, como SendGetRequest, SendPostRequeste Request. Estas funções são responsáveis por enviar pedidos GET e POST para uma API, processar as respostas e lidar com eventuais erros.

//+------------------------------------------------------------------+
//|                                                      Request.mqh |
//|                                    Copyright 2023, Lejjo Digital |
//|                           https://www.mql5.com/pt/users/14134597 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Lejjo Digital"
#property link      "https://www.mql5.com/pt/users/14134597"
#property version   "1.00"

#define ERR_HTTP_ERROR_FIRST        ERR_USER_ERROR_FIRST+1000 //+511

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000, bool debug=false)
  {
   char data[];
   uchar result[];
   string result_headers;
   int res = -1;

   int data_size = StringLen(query_param);

   if(data_size > 0)
     {
      StringToCharArray(query_param, data, 0, data_size);
      res = WebRequest("GET", url + "?" + query_param, NULL, NULL, timeout, data, data_size, result, result_headers);
     }
   else
     {
      res = WebRequest("GET", url, headers, timeout, data, result, result_headers);
     }

   if(res >= 200 && res <= 204)  // OK
     {
      //--- delete BOM
      int start_index = 0;
      int size = ArraySize(result);
      for(int i = 0; i < fmin(size, 8); i++)
        {
         if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
            start_index = i + 1;
         else
            break;
        }
      out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);

      if(debug)
         Print(out);

      return res;
     }
   else
     {
      if(res == -1)
        {
         return (_LastError);
        }
      else
        {
         //--- HTTP errors
         if(res >= 100 && res <= 511)
           {
            out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);

            if(debug)
               Print(out);

            return res;
           }
         return (res);
        }
     }

   return (0);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int SendPostRequest(const string url, const string payload, string &out, string headers = "", const int timeout = 5000, bool debug=false)
  {
   char data[];
   uchar result[];
   string result_headers;
   int res = -1;

   ArrayResize(data, StringToCharArray(payload, data, 0, WHOLE_ARRAY) - 1);

   if(headers == "")
     {
      headers = "Content-Type: application/json\r\n";
     }

   res = WebRequest("POST", url, headers, timeout, data, result, result_headers);

   if(res >= 200 && res <= 204)  // OK
     {
      //--- delete BOM
      int start_index = 0;
      int size = ArraySize(result);
      for(int i = 0; i < fmin(size, 8); i++)
        {
         if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
            start_index = i + 1;
         else
            break;
        }
      out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);

      if(debug)
         Print(out);

      return res;
     }
   else
     {
      if(res == -1)
        {
         return (_LastError);
        }
      else
        {
         //--- HTTP errors
         if(res >= 100 && res <= 511)
           {
            out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);

            if(debug)
               Print(out);

            return res;
           }
         return (res);
        }
     }

   return res;
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int Request(string method,
            string &out,
            const string url,
            const string payload = "",
            const string query_param = "",
            string headers = "",
            const int timeout = 5000)
  {
   ResetLastError();

   if(method == "GET")
     {
      return SendGetRequest(url, query_param, out, headers, timeout);
     }
   else
      if(method == "POST")
        {
         return SendPostRequest(url, payload, out, headers, timeout);
        }

   return -1;
  }
//+------------------------------------------------------------------+


Desafios e Problemas nessa abordagem:

  1. Falta de Encapsulamento e Modularidade: As funções estão atualmente dispostas de maneira isolada, sem um mecanismo claro que as agrupe por funcionalidade ou propósito. Isso resulta em um código com baixa coesão e alto acoplamento, dificultando tanto a manutenção quanto o entendimento do fluxo lógico.

  2. Reutilização Limitada do Código: Como as funções são específicas e não estão organizadas em uma estrutura modular, a reutilização do código em diferentes contextos ou projetos é limitada. Isso pode levar à duplicação de código, aumentando o risco de inconsistências e erros.

  3. Manutenção e Extensibilidade Complicadas: Sem uma clara separação de responsabilidades, a identificação e correção de bugs, assim como a adição de novas funcionalidades, se tornam tarefas complexas. Isso é especialmente problemático em projetos em expansão ou que requerem constante atualização.

Exemplos de Organização Atual das Funções:

As funções, como estão atualmente, seguem um padrão procedural. Por exemplo, a função SendGetRequestaceita parâmetros para uma URL, parâmetros de consulta e outros, e retorna um resultado baseado na resposta da WebRequest. Similarmente, SendPostRequestlida com requisições POST. A função Requestatua como um facilitador para chamar as funções GET e POST, dependendo do método HTTP especificado.

Função  SendGetRequest:

int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000, bool debug=false)
  {
   char data[];
   uchar result[];
   string result_headers;
   int res = -1;

   int data_size = StringLen(query_param);

   if(data_size > 0)
     {
      StringToCharArray(query_param, data, 0, data_size);
      res = WebRequest("GET", url + "?" + query_param, NULL, NULL, timeout, data, data_size, result, result_headers);
     }
   else
     {
      res = WebRequest("GET", url, headers, timeout, data, result, result_headers);
     }

   if(res >= 200 && res <= 204)  // OK
     {
      //--- delete BOM
      int start_index = 0;
      int size = ArraySize(result);
      for(int i = 0; i < fmin(size, 8); i++)
        {
         if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
            start_index = i + 1;
         else
            break;
        }
      out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);

      if(debug)
         Print(out);

      return res;
     }
   else
     {
      if(res == -1)
        {
         return (_LastError);
        }
      else
        {
         //--- HTTP errors
         if(res >= 100 && res <= 511)
           {
            out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);

            if(debug)
               Print(out);

            return res;
           }
         return (res);
        }
     }

   return (0);
  }

Função SendPostRequest:

int SendPostRequest(const string url, const string payload, string &out, string headers = "", const int timeout = 5000, bool debug=false)
  {
   char data[];
   uchar result[];
   string result_headers;
   int res = -1;

   ArrayResize(data, StringToCharArray(payload, data, 0, WHOLE_ARRAY) - 1);

   if(headers == "")
     {
      headers = "Content-Type: application/json\r\n";
     }

   res = WebRequest("POST", url, headers, timeout, data, result, result_headers);

   if(res >= 200 && res <= 204)  // OK
     {
      //--- delete BOM
      int start_index = 0;
      int size = ArraySize(result);
      for(int i = 0; i < fmin(size, 8); i++)
        {
         if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
            start_index = i + 1;
         else
            break;
        }
      out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);

      if(debug)
         Print(out);

      return res;
     }
   else
     {
      if(res == -1)
        {
         return (_LastError);
        }
      else
        {
         //--- HTTP errors
         if(res >= 100 && res <= 511)
           {
            out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);

            if(debug)
               Print(out);

            return res;
           }
         return (res);
        }
     }

   return res;
  }

Observe que as funções contêm vários elementos que se repetem, o que torna a manutenção de respostas a erros, por exemplo, um desafio, pois pode ser que você a aplique em uma parte e negligencie outra, ou situações semelhantes.

Esta abordagem, embora funcional, não aproveita os benefícios da orientação a objetos, como o encapsulamento e a modularidade. Cada função opera de maneira relativamente independente, sem uma estrutura unificada que as conecte ou gerencie seu comportamento de forma coesa.


A Importância da Programação Orientada a Objetos (POO)

A Programação Orientada a Objetos (POO) é um paradigma de programação que utiliza "objetos" como elementos fundamentais. Estes objetos são estruturas de dados compostas por campos de dados e procedimentos, denominados métodos, que representam entidades ou conceitos do mundo real. Na POO, cada objeto é capaz de receber e enviar mensagens, além de processar dados, atuando como uma unidade autônoma com funções ou responsabilidades específicas dentro do sistema de software.

Benefícios da POO na Manutenção e Escalabilidade em Projetos:

  1. Manutenção Simplificada: A POO facilita a manutenção de software devido ao seu design modular. Cada objeto é uma entidade independente com sua própria lógica e dados, o que significa que alterações em um objeto específico geralmente não afetam outros. Isso torna o processo de atualização, correção de bugs e aprimoramento do sistema muito mais gerenciável.

  2. Escalabilidade Aprimorada: A POO permite que os desenvolvedores construam sistemas que podem ser facilmente escalados em tamanho e complexidade. A adição de novas funcionalidades se torna mais eficiente, pois os novos objetos podem ser criados com características específicas, sem necessidade de alterar muito o código existente.

  3. Reutilização de Código: A herança, um dos pilares da POO, permite que os desenvolvedores criem novas classes com base em classes existentes. Isso promove a reutilização de código, reduz a redundância e facilita a manutenção.

Como a Modularidade Ajuda a Melhorar o Código?

A modularidade é um dos principais benefícios da POO. Ela permite aos desenvolvedores:

  1. Dividir Sistemas Complexos: Com a POO, um sistema complexo pode ser dividido em componentes menores e mais gerenciáveis (objetos), cada um com responsabilidades bem definidas. Isso torna o sistema mais fácil de entender, desenvolver e manter.

  2. Foco na Abstração: A modularidade permite aos desenvolvedores focar na abstração, trabalhando com conceitos de alto nível em vez de detalhes de baixo nível. Isso facilita a solução de problemas complexos e melhora a clareza do código.

  3. Promover a Flexibilidade e a Extensibilidade: Objetos e classes podem ser projetados para serem flexíveis e extensíveis, permitindo que um sistema evolua e se adapte com o tempo sem a necessidade de uma reescrita completa.

  4. Encorajar a Colaboração: Em um ambiente de desenvolvimento colaborativo, diferentes equipes ou desenvolvedores podem trabalhar em diferentes módulos ou objetos simultaneamente, aumentando a eficiência e reduzindo o tempo de desenvolvimento.

A adoção da POO em nosso projeto com integração RestAPI oferece uma abordagem robusta para lidar com a complexidade do software, melhorando significativamente a manutenibilidade, escalabilidade e a qualidade geral do código.


Refatoração das Funções em Classes

Agora que entendemos a importância da programação orientada a objetos (POO) e como ela pode melhorar a manutenção e escalabilidade de nossos projetos, vamos refatorar as funções existentes em classes. Para ilustrar melhor esse processo, incluiremos um diagrama que mostrará como o novo código orientado a objetos ficará mais organizado e claro. Vamos seguir um processo passo a passo para transformar nosso código procedural em um código orientado a objetos mais organizado e claro.


Implementação:

Passo 1: Definir Interfaces, começamos definindo interfaces para nossos objetos que descreverão os métodos e funcionalidades que eles devem ter. No nosso caso, temos duas interfaces: IHttpRequest e IHttpResponseProcessor. Essas interfaces estabelecem os contratos que nossas classes concretas devem seguir.

//+------------------------------------------------------------------+
//| Interface para HttpRequest                                      |
//+------------------------------------------------------------------+
interface IHttpRequest
{
public:
   virtual int Request(string method, string &out, const string url, const string payload = "", const string query_param = "") = 0;
   virtual int ValidateMethod(string method) = 0;
   virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) = 0;
   virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) = 0;
};

//+------------------------------------------------------------------+
//| Interface para HttpResponseProcessor                            |
//+------------------------------------------------------------------+
interface IHttpResponseProcessor
{
public:
   virtual int ProcessResponse(int res, string &out, uchar &result[]) = 0;
   virtual int ProcessSuccessResponse(string &out, uchar &result[]) = 0;
   virtual int ProcessErrorResponse(int res, string &out, uchar &result[]) = 0;
   virtual int DetectAndSkipBOM(uchar &result[], int size) = 0;
};

Passo 2: Criar Classes Abstratas, criamos classes abstratas que implementam essas interfaces. Essas classes abstratas não têm uma implementação real dos métodos, mas definem sua estrutura. As classes abstratas são HttpResponseProcessorBase e HttpRequestBase.

//+------------------------------------------------------------------+
//| Classe base abstrata para HttpResponseProcessor                  |
//+------------------------------------------------------------------+
class HttpResponseProcessorBase : public IHttpResponseProcessor
{
public:
   HttpResponseProcessorBase() {}
   virtual int ProcessResponse(int res, string &out, uchar &result[]) override = 0;
   virtual int ProcessSuccessResponse(string &out, uchar &result[]) override = 0;
   virtual int ProcessErrorResponse(int res, string &out, uchar &result[]) override = 0;
   virtual int DetectAndSkipBOM(uchar &result[], int size) override = 0;
};

//+------------------------------------------------------------------+
//| Classe base abstrata para HttpRequest                            |
//+------------------------------------------------------------------+
class HttpRequestBase : public IHttpRequest
{
protected:
   string m_headers;
   int m_timeout;
   IHttpResponseProcessor *responseProcessor;

public:
   HttpRequestBase(string headers = "", int timeout = 5000) : m_headers(headers), m_timeout(timeout)
   {
      if (responseProcessor == NULL)
      {
         responseProcessor = new HttpResponseProcessor();
      }
   }
   virtual int Request(string method, string &out, const string url, const string payload = "", const string query_param = "") override;
   virtual int ValidateMethod(string method) override;
   virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) override = 0;
   virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) override = 0;
   virtual int ProcessResponse(int res, string &out, uchar &result[]) = 0;
};

Classe HttpRequestBase :

  1. HttpRequestBase(string headers = "", int timeout = 5000) : Este é o construtor da classe HttpRequestBase . Ele aceita dois parâmetros opcionais, headerstimeout, que especificam os cabeçalhos HTTP a serem enviados nas solicitações e o tempo limite para a resposta, respectivamente. O construtor inicializa esses valores e cria uma instância da classe HttpResponseProcessor(a classe que processa as respostas HTTP).

  2. virtual int Request(string method, string &out, const string url, const string payload = "", const string query_param = "") : Este é um método virtual que permite fazer uma solicitação HTTP. Ele aceita o método HTTP (GET ou POST), a URL de destino, um possível corpo da solicitação ( payload), e parâmetros de consulta ( query_param). Ele coordena a chamada para as funções PerformGetRequestou PerformPostRequestcom base no método especificado e depois processa a resposta usando o método ProcessResponse.

  3. virtual int ValidateMethod(string method) : Este método verifica se o método HTTP especificado é válido (GET ou POST). Retorna verdadeiro se for válido e falso caso contrário.

  4. virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) : Este é um método virtual abstrato que deve ser implementado pelas classes derivadas. Ele realiza uma solicitação HTTP GET para a URL especificada e retorna os dados da resposta no parâmetro data, o resultado no parâmetro result, e os cabeçalhos da resposta em result_headers.

  5. virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) : Este é um método virtual abstrato que deve ser implementado pelas classes derivadas. Ele realiza uma solicitação HTTP POST para a URL especificada com o corpo da solicitação ( payload) e retorna os dados da resposta no parâmetro data, o resultado no parâmetro result, e os cabeçalhos da resposta em result_headers.

  6. virtual int ProcessResponse(int res, string &out, uchar &result[]) : Este é um método virtual abstrato que deve ser implementado pelas classes derivadas. Ele processa a resposta HTTP com base no código de resposta res . Se a resposta for bem-sucedida (código de resposta entre 200 e 299), ele chama ProcessSuccessResponse. Caso contrário, chama ProcessErrorResponse. O resultado é armazenado em out, e os dados brutos da resposta estão em result.


Passo 3: Criar Classes Concretas, criamos classes concretas que implementam os métodos das interfaces. As classes concretas são HttpRequest e HttpResponseProcessor.

//+------------------------------------------------------------------+
//| Classe concreta para HttpRequest                                 |
//+------------------------------------------------------------------+
class HttpRequest : public HttpRequestBase
{
public:
   HttpRequest(string headers = "", int timeout = 5000) : HttpRequestBase(headers, timeout) {}

   virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) override;
   virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) override;
   virtual int ProcessResponse(int res, string &out, uchar &result[]) override;
};

//+------------------------------------------------------------------+
//| Classe concreta para HttpResponseProcessor                       |
//+------------------------------------------------------------------+
class HttpResponseProcessor : public HttpResponseProcessorBase
{
public:
   virtual int ProcessResponse(int res, string &out, uchar &result[]) override;
   virtual int ProcessSuccessResponse(string &out, uchar &result[]) override;
   virtual int ProcessErrorResponse(int res, string &out, uchar &result[]) override;
   virtual int DetectAndSkipBOM(uchar &result[], int size) override;
};


Passo 4: Implementar Métodos das Classes Concretas, implementamos os métodos das classes concretas com as funcionalidades reais. Aqui, temos os métodos PerformGetRequest, PerformPostRequest, ProcessResponse, ProcessSuccessResponse, ProcessErrorResponse e DetectAndSkipBOM.

int HttpRequest::PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param)
{
   if (StringLen(query_param) > 0)
      return WebRequest("GET", url + "?" + query_param, NULL, NULL, m_timeout, data, StringLen(query_param), result, result_headers);

   return WebRequest("GET", url, m_headers, m_timeout, data, result, result_headers);
}

int HttpRequest::PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload)
{
   if (m_headers == "")
      m_headers = "Content-Type: application/json\r\n";
   ArrayResize(data, StringToCharArray(payload, data, 0, WHOLE_ARRAY) - 1);
   return WebRequest("POST", url, m_headers, m_timeout, data, result, result_headers);
}

int HttpRequest::ProcessResponse(int res, string &out, uchar &result[])
{
   if (res >= 200 && res <= 299)
      return responseProcessor.ProcessSuccessResponse(out, result);

   return responseProcessor.ProcessErrorResponse(res, out, result);
}

int HttpResponseProcessor::ProcessResponse(int res, string &out, uchar &result[])
{
   if (res >= 200 && res <= 299)
      return ProcessSuccessResponse(out, result);

   return ProcessErrorResponse(res, out, result);
}

int HttpResponseProcessor::ProcessSuccessResponse(string &out, uchar &result[])
{
   int size = ArraySize(result);
   int start_index = DetectAndSkipBOM(result, size);
   out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);
   return 0;
}

int HttpResponseProcessor::ProcessErrorResponse(int res, string &out, uchar &result[])
{
   ResetLastError();
   if (res == -1)
      return GetLastError();
   else if (res >= 100 && res <= 511)  // Erros HTTP
   {
      out = CharArrayToString(result);
      Print(out);
      return res;
   }
   return res;
}

int HttpResponseProcessor::DetectAndSkipBOM(uchar &result[], int size)
{
   int start_index = 0;
   for (int i = 0; i < MathMin(size, 3); i++)
   {
      if (result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
         start_index = i + 1;
      else
         break;
   }
   return start_index;
}

Classe  HttpRequest :

  1. HttpRequest(string headers = "", int timeout = 5000) : Este é o construtor da classe  HttpRequest. Ele chama o construtor da classe base  HttpRequestBase para inicializar as configurações de cabeçalho e tempo limite.

  2. virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) : Esta é a implementação do método  PerformGetRequest na classe  HttpRequest. Ela realiza uma solicitação HTTP GET para a URL especificada, incluindo os parâmetros de consulta, se houver. Os dados brutos da resposta são armazenados em  data, o resultado é armazenado em  result, e os cabeçalhos da resposta são armazenados em  result_headers.

  3. virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) : Esta é a implementação do método  PerformPostRequest na classe  HttpRequest. Ela realiza uma solicitação HTTP POST para a URL especificada, incluindo o corpo da solicitação ( payload). Os dados brutos da resposta são armazenados em  data, o resultado é armazenado em  result, e os cabeçalhos da resposta são armazenados em  result_headers.

  4. virtual int ProcessResponse(int res, string &out, uchar &result[]) : Esta é a implementação do método  ProcessResponse na classe  HttpRequest. Ela chama  ProcessSuccessResponse se a resposta for bem-sucedida (código de resposta entre 200 e 299) e  ProcessErrorResponse caso contrário. O resultado é armazenado em  out , e os dados brutos da resposta estão em  result.


Benefícios da Refatoração:

A refatoração do código do nosso projeto, onde passamos de uma abordagem procedural para uma orientada a objetos, traz vários benefícios significativos. Vamos discutir esses benefícios comparando o código anterior com o novo código que utiliza classes, focando em como essa mudança melhora a legibilidade, manutenibilidade e adaptabilidade do código.

Comparação entre o Código Anterior e o Novo Código com Classes:

Código Anterior (Procedural):

  • Estrutura: O código consistia em funções independentes ( SendGetRequest , SendPostRequest , Request ) que lidavam com diferentes aspectos das requisições HTTP.
  • Manutenibilidade: Alterações em uma função poderiam exigir mudanças semelhantes em outras, pois o código era repetitivo e não havia compartilhamento eficiente de lógica comum.
  • Legibilidade: Embora cada função fosse relativamente simples, o entendimento do código como um todo era mais desafiador, especialmente para novos desenvolvedores.

Novo Código (Orientado a Objetos):

  • Estrutura: Introdução de interfaces ( IHttpRequest , IHttpResponseProcessor ) e classes abstratas ( HttpRequestBase , HttpResponseProcessorBase ), seguidas por implementações concretas ( HttpRequest , HttpResponseProcessor ).
  • Manutenibilidade: O código agora é mais modular, com responsabilidades claramente definidas para cada classe. Isso facilita a atualização e o ajuste do código, pois as alterações em uma classe geralmente não afetam outras.
  • Legibilidade: A organização em classes e métodos torna o código mais intuitivo. Cada classe e método tem um propósito claro, tornando mais fácil entender o que o código faz e como ele funciona.

Melhoria na Legibilidade e Manutenibilidade:

Legibilidade:

  • Organização Lógica: O código agora é dividido em classes com funções específicas, tornando mais fácil de entender a relação entre diferentes partes do código.
  • Nomes Descritivos: Com a utilização de classes e métodos, os nomes podem ser mais descritivos, indicando claramente a funcionalidade de cada parte do código.

Manutenibilidade:

  • Facilidade de Atualização: As mudanças em uma parte do código (como a lógica de processamento de resposta HTTP) podem ser feitas em um único lugar, sem a necessidade de alterar múltiplas funções espalhadas pelo código.
  • Extensibilidade: Adicionar novas funcionalidades ou adaptar o código para novos requisitos é mais simples, pois a estrutura orientada a objetos é projetada para expansão e flexibilidade.
Adaptabilidade a Mudanças Futuras:
  • Escalabilidade: À medida que o projeto cresce, adicionar novas funcionalidades ou integrar com outras APIs e sistemas é mais fácil. As classes podem ser estendidas ou novas classes podem ser criadas com base nas existentes.
  • Reuso de Código: Componentes podem ser reutilizados em diferentes partes do projeto ou até mesmo em outros projetos, economizando tempo e esforço.
  • Facilidade de Testes: Testar o código se torna mais fácil, pois é possível focar em unidades específicas (classes ou métodos) de forma isolada.

A refatoração do nosso código para uma abordagem orientada a objetos foi uma mudança estratégica que não só aprimorou a qualidade atual do nosso projeto, mas também estabeleceu uma fundação sólida para o seu desenvolvimento futuro. Com essa transformação, alcançamos um código mais limpo, mais fácil de entender, manter e expandir.

Ao encapsular a lógica em classes bem definidas, reduzimos a redundância, melhoramos a clareza e aumentamos a eficiência do nosso código. Isso é especialmente importante em um ambiente em constante mudança, onde a adaptabilidade e a capacidade de responder rapidamente a novos requisitos é importante.

Além disso, a modularidade obtida com a programação orientada a objetos facilita a colaboração em equipe, onde diferentes partes do projeto podem ser trabalhadas simultaneamente com menos risco de conflitos de código. Também abre portas para práticas de desenvolvimento mais avançadas, testes unitários, que são mais fáceis de implementar em uma estrutura orientada a objetos.

//+------------------------------------------------------------------+
//|                                                     Requests.mqh |
//|                                    Copyright 2023, Lejjo Digital |
//|                           https://www.mql5.com/pt/users/14134597 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Lejjo Digital"
#property link      "https://www.mql5.com/pt/users/14134597"
#property version   "1.05"

//+------------------------------------------------------------------+
//| Interface para HttpRequest                                      |
//+------------------------------------------------------------------+
interface IHttpRequest
  {
public:
   virtual int       Request(string method, string &out, const string url, const string payload = "", const string query_param = "") = 0;
   virtual int       ValidateMethod(string method) = 0;
   virtual int       PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) = 0;
   virtual int       PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) = 0;
  };



//+------------------------------------------------------------------+
//| Interface para HttpResponseProcessor                            |
//+------------------------------------------------------------------+
interface IHttpResponseProcessor
  {
public:
   virtual int       ProcessResponse(int res, string &out, uchar &result[]) = 0;
   virtual int       ProcessSuccessResponse(string &out, uchar &result[]) = 0;
   virtual int       ProcessErrorResponse(int res, string &out, uchar &result[]) = 0;
   virtual int       DetectAndSkipBOM(uchar &result[], int size) = 0;
  };



//+------------------------------------------------------------------+
//| Classe base abstrata para HttpResponseProcessor                  |
//+------------------------------------------------------------------+
class HttpResponseProcessorBase : public IHttpResponseProcessor
  {
public:
                     HttpResponseProcessorBase() {};
   virtual int       ProcessResponse(int res, string &out, uchar &result[]) override = 0;
   virtual int       ProcessSuccessResponse(string &out, uchar &result[]) override = 0;
   virtual int       ProcessErrorResponse(int res, string &out, uchar &result[]) override = 0;
   virtual int       DetectAndSkipBOM(uchar &result[], int size) override = 0;
  };



//+------------------------------------------------------------------+
//| Classe base abstrata para HttpRequest                            |
//+------------------------------------------------------------------+
class HttpRequestBase : public IHttpRequest
  {
protected:
   string            m_headers;
   int               m_timeout;
   IHttpResponseProcessor *responseProcessor;

public:
                     HttpRequestBase(string headers = "", int timeout = 5000) : m_headers(headers), m_timeout(timeout)
     {

      if(responseProcessor == NULL)
        {
         responseProcessor = new HttpResponseProcessor();
        }

     }
   virtual int       Request(string method, string &out, const string url, const string payload = "", const string query_param = "") override;
   virtual int       ValidateMethod(string method) override;
   virtual int       PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) override = 0;
   virtual int       PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) override = 0;
   virtual int       ProcessResponse(int res, string &out, uchar &result[]) = 0;
  };
//+------------------------------------------------------------------+
//| Implementação da função Request na classe HttpRequestBase       |
//+------------------------------------------------------------------+
int HttpRequestBase::Request(string method, string &out, const string url, const string payload, const string query_param) override
  {
   if(!ValidateMethod(method))
     {
      out = "Método HTTP inválido.";
      return -1;
     }

   char data[];
   uchar result[];
   string result_headers;
   int res = -1;

   if(method == "GET")
      res = PerformGetRequest(data, result, result_headers, url, query_param);
   else
      if(method == "POST")
         res = PerformPostRequest(data, result, result_headers, url, payload);

   if(res >= 0)
      return ProcessResponse(res, out, result);
   else
     {
      out = "Erro ao realizar a solicitação HTTP.";
      return res;
     }
  }
//+------------------------------------------------------------------+
//| Implementação da função ValidateMethod na classe HttpRequestBase |
//+------------------------------------------------------------------+
int HttpRequestBase::ValidateMethod(string method)
  {
   return (method == "GET" || method == "POST");
  }



//+------------------------------------------------------------------+
//| Classe concreta para HttpRequest                                 |
//+------------------------------------------------------------------+
class HttpRequest : public HttpRequestBase
  {
public:
                     HttpRequest(string headers = "", int timeout = 5000) : HttpRequestBase(headers, timeout) {}

   virtual int       PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param) override;
   virtual int       PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload) override;
   virtual int       ProcessResponse(int res, string &out, uchar &result[]) override;
  };
//+------------------------------------------------------------------+
//| Implementação das funções da classe HttpRequest                  |
//+------------------------------------------------------------------+
int HttpRequest::PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param)
  {
   if(StringLen(query_param) > 0)
      return WebRequest("GET", url + "?" + query_param, NULL, NULL, m_timeout, data, StringLen(query_param), result, result_headers);

   return WebRequest("GET", url, m_headers, m_timeout, data, result, result_headers);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int HttpRequest::PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload)
  {
   if(m_headers == "")
      m_headers = "Content-Type: application/json\r\n";
   ArrayResize(data, StringToCharArray(payload, data, 0, WHOLE_ARRAY) - 1);
   return WebRequest("POST", url, m_headers, m_timeout, data, result, result_headers);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int HttpRequest::ProcessResponse(int res, string &out, uchar &result[])
  {
   if(res >= 200 && res <= 299)
      return responseProcessor.ProcessSuccessResponse(out, result);

   return responseProcessor.ProcessErrorResponse(res, out, result);
  }



//+------------------------------------------------------------------+
//| Classe concreta para HttpResponseProcessor                       |
//+------------------------------------------------------------------+
class HttpResponseProcessor : public HttpResponseProcessorBase
  {
public:
   virtual int       ProcessResponse(int res, string &out, uchar &result[]) override;
   virtual int       ProcessSuccessResponse(string &out, uchar &result[]) override;
   virtual int       ProcessErrorResponse(int res, string &out, uchar &result[]) override;
   virtual int       DetectAndSkipBOM(uchar &result[], int size) override;
  };
//+------------------------------------------------------------------+
//| Implementação das funções da classe HttpResponseProcessor       |
//+------------------------------------------------------------------+
int       HttpResponseProcessor::ProcessResponse(int res, string &out, uchar &result[])
  {
   if(res >= 200 && res <= 299)
      return ProcessSuccessResponse(out, result);

   return ProcessErrorResponse(res, out, result);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int       HttpResponseProcessor::ProcessSuccessResponse(string &out, uchar &result[]) override
  {
   int size = ArraySize(result);
   int start_index = DetectAndSkipBOM(result, size);
   out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);
   return 0;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int       HttpResponseProcessor::ProcessErrorResponse(int res, string &out, uchar &result[]) override
  {
   ResetLastError();
   if(res == -1)
      return GetLastError();
   else
      if(res >= 100 && res <= 511)  // Erros HTTP
        {
         out = CharArrayToString(result);
         Print(out);
         return res;
        }
   return res;
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int       HttpResponseProcessor::DetectAndSkipBOM(uchar &result[], int size) override
  {
   int start_index = 0;
   for(int i = 0; i < MathMin(size, 3); i++)
     {
      if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
         start_index = i + 1;
      else
         break;
     }
   return start_index;
  };
//+------------------------------------------------------------------+


Exemplos de Uso das Classes

Nesta seção, apresentamos exemplos práticos de como utilizar as classes criadas para realizar requisições HTTP no MQL5. Esses exemplos são importantes para ilustrar a reutilização de código e a eficiência na criação de novas funcionalidades.

Teste da Resposta de Sucesso:

void TestProcessSuccessResponse()
{
    HttpResponseProcessor processor;
    string output;
    uchar result[];

    // Simulando uma resposta de sucesso em formato JSON
    string mockResponse = "{\"status\": \"success\", \"data\": \"Sample data\"}";
    StringToCharArray(mockResponse, result);

    // Processando a resposta simulada
    processor.ProcessSuccessResponse(output, result);

    // Verificando a saída
    Print("Teste de Sucesso: ", output);
}

Explicação:

  • HttpResponseProcessor processor : Criação de um objeto processor da classe HttpResponseProcessor .
  • StringToCharArray : Converte a string de resposta simulada para um array de caracteres.
  • processor.ProcessSuccessResponse(output, result) : Chama o método para processar a resposta simulada.

Teste da Resposta de Erro:

void TestProcessErrorResponse()
{
    HttpResponseProcessor processor;
    string output;
    uchar result[];

    // Simulando uma resposta de erro (404 Not Found)
    string mockResponse = "404 Not Found";
    StringToCharArray(mockResponse, result);

    // Processando a resposta de erro
    processor.ProcessErrorResponse(404, output, result);

    // Verificando a saída
    Print("Teste de Erro: ", output);
}

Explicação:

  • Este exemplo é semelhante ao anterior, mas foca em simular e processar uma resposta de erro HTTP.

Teste de Detecção e Remoção do BOM:

void TestDetectAndSkipBOM()
{
    HttpResponseProcessor processor;
    uchar result[6] = {0xEF, 0xBB, 0xBF, 'a', 'b', 'c'}; // 'abc' com BOM UTF-8

    // Detectando e pulando o BOM (Byte Order Mark)
    int startIndex = processor.DetectAndSkipBOM(result, ArraySize(result));

    // Verificando o índice inicial após o BOM
    Print("Índice de início após BOM: ", startIndex); // Esperado: 3
}

Explicação:

  • uchar result[6] = {0xEF, 0xBB, 0xBF, 'a', 'b', 'c'}; : Cria um array com o BOM UTF-8 seguido por 'abc'.
  • processor.DetectAndSkipBOM(result, ArraySize(result)); : Detecta e pula o BOM, retornando o índice do início do conteúdo relevante.

Execução dos Testes e Requisição HTTP GET:

int OnInit()
{
    RunTests(); // Executa os testes

    HttpRequest httpRequest("", 5000); // Cria uma instância da classe HttpRequest
    string output; // Variável para armazenar a saída

    //Realizando um Pedido GET
    int responseCode = httpRequest.Request("GET", output, "https://jsonplaceholder.typicode.com/posts/1");

    // Exibindo o resultado
    Print("Código de Resposta: ", responseCode);
    Print("Saída: ", output);
}

Explicação:

  • HttpRequest httpRequest("", 5000) : Cria um objeto httpRequest da classe HttpRequest com configurações padrão.
  • httpRequest.Request("GET", output, "https://...") : Faz uma requisição GET ao URL fornecido e armazena a resposta na variável output .

Esses exemplos mostram como as classes HttpResponseProcessor e HttpRequest podem ser utilizadas para lidar com diferentes aspectos das respostas HTTP, como sucesso, erro e a presença de BOM. Além disso, demonstram a facilidade de fazer requisições GET com a classe HttpRequest .

A modularização do código em classes é uma abordagem fundamental na programação que possibilita a criação de um sistema organizado e compreensível. Essa prática envolve a divisão do código em unidades independentes chamadas de classes, cada uma com sua própria responsabilidade e funcionalidade específica.

Ao adotar essa técnica, os desenvolvedores podem estruturar seu código de forma mais lógica e clara, tornando-o mais legível e de fácil compreensão. Isso significa que, em vez de lidar com um código monolítico e desorganizado, o desenvolvedor trabalha com pequenas partes do sistema, cada uma representada por uma classe.

A vantagem disso é que as classes podem ser projetadas de forma coesa, com métodos e atributos relacionados agrupados em conjunto. Isso não apenas torna o código mais compreensível, mas também simplifica a manutenção e o desenvolvimento futuro, já que é mais fácil localizar e corrigir problemas em unidades individuais.

Além disso, a modularização em classes promove a reutilização de código, pois as classes podem ser usadas em diferentes partes do programa, economizando tempo e esforço na criação de funcionalidades semelhantes.

Agora vamos apresentar um exemplo completo, incluindo o código de teste para demonstrar a aplicação prática das classes HttpResponseProcessor e HttpRequest . Este exemplo ajudará a ilustrar como as classes podem ser efetivamente utilizadas para realizar requisições HTTP e processar respostas, seja em um sucesso ou em um erro, oferecendo uma visão detalhada e abrangente do funcionamento do código.

//+------------------------------------------------------------------+
//|                                                         test.mq5 |
//|                                    Copyright 2023, Lejjo Digital |
//|                           https://www.mql5.com/pt/users/14134597 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Lejjo Digital"
#property link      "https://www.mql5.com/pt/users/14134597"
#property version   "1.00"

#include "Requests.mqh"


//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void TestProcessSuccessResponse()
  {
   HttpResponseProcessor processor;
   string output;
   uchar result[];

// Simular uma resposta de sucesso (exemplo com JSON)
   string mockResponse = "{\"status\": \"success\", \"data\": \"Sample data\"}";
   StringToCharArray(mockResponse, result);

// Chamar ProcessSuccessResponse
   processor.ProcessSuccessResponse(output, result);

// Verificar se a saída é como esperado
   Print("Teste de Sucesso: ", output);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void TestProcessErrorResponse()
  {
   HttpResponseProcessor processor;
   string output;
   uchar result[];

// Simular uma resposta de erro (exemplo com erro 404)
   string mockResponse = "404 Not Found";
   StringToCharArray(mockResponse, result);

// Chamar ProcessErrorResponse com um código de erro simulado
   processor.ProcessErrorResponse(404, output, result);

// Verificar se a saída é como esperado
   Print("Teste de Erro: ", output);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void TestDetectAndSkipBOM()
  {
   HttpResponseProcessor processor;
   uchar result[6] = {0xEF, 0xBB, 0xBF, 'a', 'b', 'c'}; // 'abc' com BOM UTF-8

// Chamar DetectAndSkipBOM
   int startIndex = processor.DetectAndSkipBOM(result, ArraySize(result));

// Verificar se o índice de início está correto
   Print("Índice de início após BOM: ", startIndex); // Esperado: 3
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void RunTests()
  {
   TestProcessSuccessResponse();
   TestProcessErrorResponse();
   TestDetectAndSkipBOM();
  }


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
// Executar testes HttpResponseProcessor
   RunTests();

// Criação da instância da classe HttpRequest
   HttpRequest httpRequest("", 5000);

// Variáveis para armazenar a saída
   string output;

// Realizando o pedido GET
   int responseCode = httpRequest.Request("GET", output, "https://jsonplaceholder.typicode.com/posts/1");

// Exibindo o resultado
   Print("Código de Resposta: ", responseCode);
   Print("Saída: ", output);


//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+


Conclusão

Chegamos ao final deste artigo onde exploramos a transformação de um projeto tradicional para um design orientado a objetos. A mudança do código de uma estrutura procedural para uma arquitetura baseada em classes oferece não apenas uma organização mais clara, mas também abre caminho para uma manutenção e expansão do código mais eficientes.

Relevância da Programação Orientada a Objetos no MQL5:

  • A adoção do paradigma da Programação Orientada a Objetos (POO) representa um salto qualitativo significativo no desenvolvimento de software. Em nosso contexto, onde o MQL5 é usado principalmente para trading algorítmico e automação de estratégias no mercado financeiro, a importância de um código bem estruturado e modular é ainda mais crucial.

Vantagens da Modularidade e Encapsulamento:

  • A organização do código em classes nos permite encapsular funcionalidades específicas, tornando o sistema mais intuitivo e fácil de manter. Cada classe se torna um módulo com responsabilidades definidas, o que simplifica a identificação e resolução de problemas, além de facilitar a expansão do sistema com novas funcionalidades.

Benefícios da Reutilização de Código:

  • A POO incentiva a reutilização de código. Com a criação de classes bem definidas, podemos reutilizar essas estruturas em diferentes partes do projeto ou até mesmo em outros projetos. Isso não apenas economiza tempo, mas também aumenta a consistência e a confiabilidade do código.

Facilidade de Manutenção e Escalabilidade:

  • Manter e escalar um projeto se torna muito mais viável com a POO. À medida que seu projeto cresce ou se adapta a novos requisitos, a capacidade de modificar um componente específico sem afetar o resto do sistema é uma vantagem inestimável.

Encorajo todos os leitores, independentemente do nível de experiência em programação, a aplicarem os conceitos de POO em seus próprios projetos MQL5. A transição para a POO pode parecer desafiadora no início, mas os benefícios de longo prazo em termos de qualidade do código, eficiência de desenvolvimento e facilidade de manutenção são inegáveis.

Teoria das Categorias em MQL5 (Parte 15): Funtores com grafos Teoria das Categorias em MQL5 (Parte 15): Funtores com grafos
Este artigo continua a série sobre a implementação da teoria de categorias no MQL5, ele aborda os funtores como uma ponte entre grafos e conjuntos. Nesse escopo, voltaremos a analisar os dados de calendário e, apesar de suas limitações no uso do testador de estratégias, justificaremos o uso de funtores na previsão de volatilidade mediante correlação.
Estratégia de negociação RSI Deep Three Move Estratégia de negociação RSI Deep Three Move
Este artigo apresenta a estratégia de negociação RSI Deep Three Move no MetaTrader 5. O artigo é baseado em uma nova série de pesquisas que demonstram vários métodos de negociação com base no RSI, que é um indicador técnico para medir a força e o impulso de ativos financeiros, incluindo ações, moedas e commodities.
Redes neurais de maneira fácil (Parte 52): exploração com otimização e correção de distribuição Redes neurais de maneira fácil (Parte 52): exploração com otimização e correção de distribuição
À medida que a política do Ator se afasta cada vez mais dos exemplos armazenados no buffer de reprodução de experiências, a eficácia do treinamento do modelo, baseado nesse buffer, diminui. Neste artigo, examinamos um algoritmo que aumenta a eficácia do uso de amostras em algoritmos de aprendizado por reforço.
Operações de negociação Estruturas das solicitações e das resposta, descrição e registro Operações de negociação Estruturas das solicitações e das resposta, descrição e registro
Neste artigo, veremos como trabalhar com as estruturas das solicitações de negociação, criar a solicitação, verificá-la antes de enviá-la ao servidor, gerar a resposta do servidor quanto a ela e usar a estrutura das transações. Além disso, criaremos funções simples e convenientes para enviar ordens para o servidor e, com base em tudo o que foi mencionado acima, criar um Expert Advisor que informe sobre as transações.