Español Português
preview
Как разработать агент обучения с подкреплением на MQL5 с интеграцией RestAPI (Часть 4): Организация функций в классах в MQL5

Как разработать агент обучения с подкреплением на MQL5 с интеграцией RestAPI (Часть 4): Организация функций в классах в MQL5

MetaTrader 5Примеры | 18 июня 2024, 16:22
237 0
Jonathan Pereira
Jonathan Pereira

Введение

Добро пожаловать в четвертую часть нашей серии, в которой мы исследуем создание агента обучения с подкреплением на MQL5 с интеграцией RestAPI. До сегодняшней статьи мы рассматривали такие важные аспекты, как использование RestAPI в MQL5, создание MQL5-функций для взаимодействия с REST API в крестики-нолики, а также выполнение автоматических ходов и тестовых скриптов. Это дало нам прочную основу и помогло понять, как MQL5 взаимодействует с внешними элементами.

В этой статье мы сделаем важный шаг и организуем наши функции в классы в MQL5. Для этого, мы будем использовать объектно-ориентированное программирование, которое представляет собой способ написания кода, помогающий сохранить его организованность и простоту понимания. Это важно, поскольку облегчает нам обслуживание и улучшение кода. Хорошо организованный и модульный код мы сможем использовать в разных частях проекта или даже в будущих проектах.

В оставшейся части статьи мы расскажем, как реструктурировать существующие MQL5-функции в классы. Мы покажем, как это может сделать код более читабельным и эффективным, и также приведем практические примеры того, как это сделать, показывая как это может облегчить обслуживание и улучшение кода.

Объектно-ориентированное программирование (ООП) - это мощный способ разработки программного обеспечения. В MQL5 использование классов является большим превосходством по сравнению с процедурным методом написания кода. В этой части мы рассмотрим, как улучшить качество нашего проекта с помощью этой характеристики. Давайте рассмотрим четыре важных аспекта:

  1. Инкапсуляция и модульность: Классы помогают организовать связанные функции и переменные в одном месте, что упрощает обслуживание и уменьшает количество ошибок.

  2. Повторное использование кода: Написав класс, можно использовать его в разных местах, что экономит время и поддерживает согласованность кода.

  3. Простота обслуживания и улучшения: Если функции разделены на классы, проще найти и исправить ошибки или внести улучшения, так как четкая структура делает код более доступным.

  4. Абстракция и гибкость: Классы способствуют абстрагированию, скрывая сложность и раскрывая только то, что нам необходимо. Это делает код более интуитивным и гибким.

Мы покажем, что перестановка функций в классы в MQL5 - это не только красоты ради, это значительное изменение, которое делает код более эффективным, простым для понимания и обслуживания. Мы расскажем о преобразовании изолированных функций в четко определенные методы класса, что даст преимущества и сразу, и в долгосрочной перспективе. Это не только улучшит наш текущий проект, но и поможет нам создать прочный фундамент для будущих проектов на MQL5.


Текущее состояние кода 

В текущем состоянии наш код состоит из ряда функций для обработки HTTP-запросов, таких как SendGetRequest, SendPostRequest и Request. Данные функции отвечают за отправку GET- и POST-запросов к API, обработку ответов и устранение возможных ошибок.

//+------------------------------------------------------------------+
//|                                                      Request.mqh |
//|                                    Copyright 2023, Lejjo Digital |
//|                           https://www.mql5.com/ru/users/14134597 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Lejjo Digital"
#property link      "https://www.mql5.com/ru/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;
  }
//+------------------------------------------------------------------+


Вызовы и проблемы данного подхода:

  1. Отсутствие инкапсуляции и модульности: В настоящее время функции расположены изолированно, без четкого механизма для их группировки по функциональности или назначению. В результате получается код с низкой сплоченностью и высокой связностью, что усложняет обслуживание и понимание логического потока.

  2. Ограниченное повторное использование кода: Поскольку функции специфичны и не организованы в модульную структуру, повторное использование кода в различных контекстах или проектах будет ограничено, что может привести к дублированию кода, что увеличивает риск возникновения несоответствий и ошибок.

  3. Сложное обслуживание и расширяемость: Без четкого разделения обязанностей, выявление и исправление ошибок, а также добавление новых функциональных возможностей превращается в сложные задачи. Особенно проблемным это становится для проектов, которые расширяются или требуют постоянного обновления.

Примеры текущей организации функций:

В текущем их состоянии функции выполняются по процедурной схеме. Например, функция SendGetRequest принимает параметры URL, параметры запроса и другие, и возвращает результат, используя как основу ответ WebRequest. Аналогичным образом SendPostRequest обрабатывает POST-запросы. Функция Request служит для облегчения вызова функций GET и POST, в зависимости от указанного нами метода HTTP.

Функция 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);
  }

Функция 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;
  }

Обратите внимание, что функции содержат несколько повторяющихся элементов, что делает сложным, например, обработку ответов на разные ошибки, так как их можно применить в одной части и игнорировать в другой, или похожие ситуации.

Данный подход, хотя и функционален, но он не использует преимущества объектной ориентации, такие как инкапсуляция и модульность. Каждая функция работает относительно независимо, без единой структуры, которая бы связывала их или управляла их поведением согласованно.


Важность ООП

ООП - это парадигма программирования, которая использует "объекты" в качестве фундаментальных строительных блоков. Эти объекты представляют собой структуры данных, состоящие из полей данных и процедур, называемых методами, которые представляют сущности или концепции реального мира. В ООП каждый объект имеет возможность принимать и отправлять сообщения, а также обрабатывать данные, действуя при этом в качестве автономной единицы с определенными функциями или обязанностями в рамках программной системы.

Преимущества ООП в обслуживании и масштабировании проектов:

  1. Упрощенное обслуживание: ООП облегчает обслуживание программного обеспечения благодаря модульной конструкции. Каждый объект - это независимая единица со своей логикой и данными, а это значит, что изменения в конкретном объекте, как правило, не влияют на другие объекты. Эта характеристика делает процесс обновления, исправления ошибок и улучшения системы гораздо более управляемым.

  2. Улучшенная масштабируемость: ООП позволяет разработчикам создавать системы, которые можно легко масштабировать по размеру и сложности. Добавление новых функциональных возможностей становится более эффективным, так как новые объекты могут быть созданы с определенными функциями без необходимости обширной модификации существующего кода.

  3. Повторное использование кода: Наследование, один из основных принципов ООП, позволяет разработчикам создавать новые классы на основе существующих. Это способствует повторному использованию кода, уменьшает избыточность и облегчает обслуживание.

Как модульность помогает улучшить наш код?

Модульность - одно из главных преимуществ ООП. Она предоставляет разработчикам следующие возможности:

  1. Разделение сложных систем: С помощью ООП сложную систему можно разбить на более мелкие, управляемые компоненты (объекты), каждый из которых имеет четко определенные обязанности. Это облегчает понимание, разработку и обслуживание системы.

  2. Акцент на абстракцию: Модульность позволяет разработчикам сосредоточиться на абстракции, работать с высокоуровневыми понятиями, а не с низкоуровневыми деталями, а это облегчает решение сложных проблем и делает код более ясным.

  3. Поощрение гибкости и расширяемости: Объекты и классы можно разработать так, чтобы они были гибкими и расширяемыми, чтобы позволить системе развиваться и адаптироваться со временем без необходимости полного переписывания.

  4. Поощрение сотрудничества: В среде совместной разработки разные команды или разработчики могут одновременно работать над отличающимися модулями или объектами, что повышает эффективность и сокращает время разработки.

Применение ООП в нашем проекте с интеграцией RestAPI обеспечивает надежный подход к управлению сложностью программного обеспечения, что значительно улучшает обслуживание, масштабируемость и общее качество кода.


Рефакторинг функций в классах

Теперь, когда мы понимаем важность ООП и то, как оно может улучшить обслуживание и масштабируемость наших проектов, предлагаю провести рефакторинг существующих функций в классы. Чтобы лучше проиллюстрировать данный процесс, мы приведем диаграмму, которая покажет, как новый объектно-ориентированный код будет более организованным и понятным. Мы будем следовать пошаговому процессу преобразования нашего процедурного кода в более организованный и понятный объектно-ориентированный код.


Реализация

Шаг 1. Определяем интерфейсы. Начнем с определения интерфейсов для наших объектов, которые будут описывать методы и функциональные возможности, которыми они должны обладать. В нашем случае у нас есть два: IHttpRequest и IHttpResponseProcessor. Данные интерфейсы устанавливают контракты, которым должны следовать наши конкретные классы.

//+------------------------------------------------------------------+
//| Interface for 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 for 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;
};

Шаг 2. Создаем абстрактные классы. Мы создаем абстрактные классы, которые реализуют эти интерфейсы. Эти классы не имеют фактической реализации методов, но определяют их структуру. Абстрактными классами являются HttpResponseProcessorBase и HttpRequestBase.

//+------------------------------------------------------------------+
//| Abstract base class for 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;
};

//+------------------------------------------------------------------+
//| Abstract base class for 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;
};

Класс HttpRequestBase:

  1. HttpRequestBase(string headers = "", int timeout = 5000): Это конструктор класса HttpRequestBase. Он принимает два необязательных параметра, headers и timeout, которые указывают HTTP-заголовки для отправки в запросах и таймаут для ответа, соответственно. Конструктор инициализирует данные значения и создает экземпляр класса HttpResponseProcessor (класс, который обрабатывает HTTP-ответы).

  2. virtual int Request(string method, string &out, const string url, const string payload = "", const string query_param = ""): Этот виртуальный метод позволяет выполнить HTTP-запрос. Принимает метод HTTP (GET или POST), URL назначения, возможное тело запроса (payload) и параметры запроса (query_param). Координирует вызов функций PerformGetRequest или PerformPostRequest на основе указанного метода, а затем обрабатывает ответ путем использования метода ProcessResponse.

  3. virtual int ValidateMethod(string method): Этот метод проверяет, действителен ли указанный метод HTTP (GET или POST). Возвращает true, если он действителен, и возвращает false в обратном случае.

  4. virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param): Этот абстрактный виртуальный метод должен быть реализован производными классами. Выполняет HTTP GET-запрос к указанному URL и возвращает данные ответа в параметре data, результат в параметре result и заголовки ответа в result_headers.

  5. virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload): Этот абстрактный виртуальный метод должен быть реализован производными классами. Выполняет HTTP POST-запрос к указанному URL с телом запроса (payload) и возвращает данные ответа в параметре data, результат в параметре result и заголовки ответа в result_headers.

  6. virtual int ProcessResponse(int res, string &out, uchar &result[]): Этот абстрактный виртуальный метод должен быть реализован производными классами. Он обрабатывает HTTP-ответ на основе кода ответа res. Если ответ успешный (код ответа находится в диапазоне от 200 до 299), вызывается ProcessSuccessResponse. В противном случае вызывает ProcessErrorResponse. Результат хранится в out, а необработанные данные ответа - в result.


Шаг 3. Создаем конкретные классы. Мы создаем конкретные классы, с помощью которых реализуются методы интерфейсов. HttpRequest и HttpResponseProcessor являются конкретными классами.

//+------------------------------------------------------------------+
//| Concrete class for 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;
};

//+------------------------------------------------------------------+
//| Concrete class for 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;
};


Шаг 4. Реализуем методы конкретных классов. Мы реализуем методы конкретных классов с реальными функциональными возможностями. Здесь у нас методы PerformGetRequest, PerformPostRequest, ProcessResponse, ProcessSuccessResponse, ProcessErrorResponse и 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;
}

Класс HttpRequest:

  1. HttpRequest(string headers = "", int timeout = 5000): Это конструктор класса HttpRequest. Вызывает конструктор базового класса HttpRequestBase для того, чтобы запускать параметры заголовка и таймаута.

  2. virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param): Это реализация метода PerformGetRequest в классе HttpRequest. Выполняет HTTP GET-запрос к указанному URL, включая параметры запроса, если такие имеются. Необработанные данные ответа сохраним в data, результат - в result, а заголовки ответа - в result_headers.

  3. virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload): Это реализация метода PerformPostRequest в классе HttpRequest. Выполняет HTTP POST-запрос на указанный URL, включая тело запроса (payload). Необработанные данные ответа сохраним в data, результат - в result, а заголовки ответа - в result_headers.

  4. virtual int ProcessResponse(int res, string &out, uchar &result[]): Это реализация метода ProcessResponse в классе HttpRequest. Вызывает ProcessSuccessResponse в случае успешного ответа (код ответа находится в диапазоне от 200 до 299), иначе ProcessErrorResponse. Результат сохраним в out, а необработанные данные ответа - в result.


Преимущества рефакторинга:

Рефакторинг кода нашего проекта, при котором мы переходим от процедурного к объектно-ориентированному подходу, показывает несколько существенных преимуществ. Мы их обсудим, сравнив старый код с новым, который использует классы, и сосредоточимся на том, как это улучшает читабельность, обслуживание и адаптивность кода.

Сравнение старого и нового кода с классами:

Предыдущий код (процедурный):

  • Структура: Код состоял из отдельных функций (SendGetRequest, SendPostRequest, Request), которые обрабатывали различные аспекты HTTP-запросов.
  • Обслуживание: Любые изменения в одной функции могли бы потребовать аналогичных изменений в других, поскольку код повторялся и не обеспечивал эффективного обмена общей логикой.
  • Читабельность: Хотя каждая функция была относительно простой, понять код в целом было сложнее, особенно для начинающих разработчиков.

Новый (объектно-ориентированный) код:

  • Структура: Введение интерфейсов (IHttpRequest, IHttpResponseProcessor) и абстрактных классов (HttpRequestBase, HttpResponseProcessorBase), а следом конкретных реализаций (HttpRequest, HttpResponseProcessor).
  • Обслуживание: Теперь код стал более модульным, с четко определенными задачами для каждого класса. Это облегчает обновление и исправление кода, так как изменения в одном классе обычно не влияют на другие классы.
  • Читабельность: Организация на классы и методы делает код более интуитивным. Все классы и методы имеют четкое назначение, что облегчает понимание того, что делает код и как он работает.

Улучшение читабельности и обслуживания:

Читабельность:

  • Логическая организация: Теперь код разделен на классы с определенными функциями, что облегчает понимание связи между различными частями кода.
  • Описательные названия: При использовании классов и методов названия могут быть более описательными, чтобы четко отражать функциональность каждой части кода.

Обслуживание:

  • Простота обновления: Изменения в одной части кода (например, в логике обработки HTTP-ответов) могут быть сделаны в одном месте, без необходимости изменять множество функций, разбросанных по всему коду.
  • Расширяемость: Добавить новую функциональность или адаптировать код к новым требованиям очень просто, поскольку объектно-ориентированная структура рассчитана на расширение и гибкость.
Адаптация к будущим изменениям:
  • Масштабируемость: По мере увеличения проекта,всё проще становится добавление новой функциональности или интеграция с другими API и системами. Классы можно расширять или создавать новые классы на основе существующих.
  • Повторное использование кода: Компоненты можно использовать снова в разных частях проекта или даже в других проектах, что экономит время и силы.
  • Простота тестирования: Тестирование кода становится проще, так как можно сосредоточиться на конкретных единицах (классах или методах) по отдельности.

Рефакторинг нашего кода в соответствии с объектно-ориентированным подходом был стратегическим изменением, которое не только улучшает текущее качество нашего проекта, но и заложило прочный фундамент для его дальнейшего развития. Благодаря этой трансформации мы получаем более чистый код, который легче понять, обслуживать и расширять.

Если инкапсулировать логику в четко определенные классы, мы сократим избыточность, улучшим ясность и повысим эффективность нашего кода. Это особенно важно в постоянно меняющейся среде, где важна гибкость и способность быстро реагировать на новые требования.

Кроме того, модульность, достигаемая при ООП, облегчает совместную работу команды, когда над разными частями проекта можно работать одновременно с меньшим риском конфликтов кода. Это также открывает двери для более продвинутых методов разработки, таких как модульное тестирование, которые легче реализовать в объектно-ориентированной структуре.

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

//+------------------------------------------------------------------+
//| Interface for 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 for 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;
  };



//+------------------------------------------------------------------+
//| Abstract base class for 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;
  };



//+------------------------------------------------------------------+
//| Abstract base class for 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;
  };
//+------------------------------------------------------------------+
//| Implement the Request function in HttpRequestBase class          |
//+------------------------------------------------------------------+
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 = "Error when making HTTP request.";
      return res;
     }
  }
//+------------------------------------------------------------------+
//| Implement the ValidateMethod function in HttpRequestBase class   |
//+------------------------------------------------------------------+
int HttpRequestBase::ValidateMethod(string method)
  {
   return (method == "GET" || method == "POST");
  }



//+------------------------------------------------------------------+
//| Concrete class for 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;
  };
//+------------------------------------------------------------------+
//| Implementation of functions for HttpRequest class                |
//+------------------------------------------------------------------+
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);
  }



//+------------------------------------------------------------------+
//| Concrete class for 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;
  };
//+------------------------------------------------------------------+
//| Implementation of functions for HttpResponseProcessor class      |
//+------------------------------------------------------------------+
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;
  };
//+------------------------------------------------------------------+


Примеры использования классов

В этом разделе мы приведем практические примеры использования классов, созданных для выполнения HTTP-запросов в MQL5. Данные примеры важны для иллюстрации повторного использования кода и эффективности создания новой функциональности.

Проверка успешности ответа:

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

    // Simulate a successful response in JSON format
    string mockResponse = "{\"status\": \"success\", \"data\": \"Sample data\"}";
    StringToCharArray(mockResponse, result);

    // Process the simulated response
    processor.ProcessSuccessResponse(output, result);

    // Check the output
    Print("Success Test: ", output);
}

Объяснение:

  • HttpResponseProcessor processor: Создание объекта процессора класса HttpResponseProcessor.
  • StringToCharArray: Преобразует строку смоделированного ответа в массив символов.
  • processor.ProcessSuccessResponse(output, result): Вызывает метод для обработки смоделированного ответа.

Тест реакции на ошибку:

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

    // Simulate an error response (404 Not Found)
    string mockResponse = "404 Not Found";
    StringToCharArray(mockResponse, result);

    // Process an error response
    processor.ProcessErrorResponse(404, output, result);

    // Check the output
    Print("Error Test: ", output);
}

Объяснение:

  • Данный пример похож на предыдущий, но он посвящен моделированию и обработке ответа на HTTP-ошибку.

Тест на обнаружение и устранение BOM:

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

    // Detect and skip the BOM (Byte Order Mark)
    int startIndex = processor.DetectAndSkipBOM(result, ArraySize(result));

    // Check the initial index after BOM
    Print("Start index after BOM: ", startIndex); // Expected: 3
}

Объяснение:

  • uchar result[6] = {0xEF, 0xBB, 0xBF, 'a', 'b', 'c'};: Создает массив с BOM UTF-8, за которым следует 'abc'.
  • processor.DetectAndSkipBOM(result, ArraySize(result));: Определяет и пропускает BOM, и далее возвращает индекс начала соответствующего содержимого.

Выполнение теста и HTTP GET-запрос:

int OnInit()
{
    RunTests(); // Run the tests

    HttpRequest httpRequest("", 5000); // Create an instance of the HttpRequest class
    string output; // Variable to store the output

    // Perform the GET request
    int responseCode = httpRequest.Request("GET", output, "https://jsonplaceholder.typicode.com/posts/1");

    // Show the result
    Print("Response Code: ", responseCode);
    Print("Output: ", output);
}

Объяснение:

  • HttpRequest httpRequest("", 5000): Создайте объект httpRequest класса HttpRequest со стандартными настройками.
  • httpRequest.Request("GET", output, "https://..."): Выполняет GET-запрос к указанному URL и сохраняет ответ в выходной переменной.

Данные примеры показывают, как можно использовать классы HttpResponseProcessor и HttpRequest для обработки различных аспектов HTTP-ответов, таких как успех, ошибка и наличие BOM. Они также демонстрируют, как легко осуществить GET-запросы с помощью класса HttpRequest.

Модулирование кода на классы - это фундаментальный подход к программированию, который позволяет создать организованную и понятную систему. Эта практика подразумевает разбиение кода на независимые единицы, называемые классами, каждый из которых несет свою ответственность и обладает определенной функциональностью.

Используя данную технику, разработчики могут более логично и четко структурировать свой код, делая его более читабельным и легким для понимания. Это означает, что вместо монолитного кода, мы имеем дело с неорганизованным кодом; разработчик работает с небольшими частями системы, каждая из которых представлена классом.

Преимущество такого подхода заключается в том, что можно спроектировать классы целостно, со связанными методами и атрибутами, сгруппированными вместе. Это не только делает код более понятным, но и упрощает обслуживание и дальнейшее развитие, поскольку легче найти и исправить проблемы в отдельных блоках.

Кроме того, модульность классов способствует повторному использованию кода, поскольку классы могут использоваться в разных местах программы, что позволяет сэкономить время и усилия на создание аналогичной функциональности.

Сейчас мы представим полный пример, который включает тестовый код для демонстрации практического применения классов HttpResponseProcessor и HttpRequest. Данный пример поможет проиллюстрировать, как можно эффективно использовать классы для выполнения HTTP-запросов и обработки ответов, как при успехе, так и при неудаче, обеспечивая тем самым детальное и полное представление о том, как работает код.

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

#include "Requests.mqh"


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

// Simulate a success report (example with JSON)
   string mockResponse = "{\"status\": \"success\", \"data\": \"Sample data\"}";
   StringToCharArray(mockResponse, result);

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

// Check that the output is as expected
   Print("Success Test: ", output);
  }

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

// Simulate an error response (exemple with erro 404)
   string mockResponse = "404 Not Found";
   StringToCharArray(mockResponse, result);

// Call ProcessErrorResponse with a simulated error code
   processor.ProcessErrorResponse(404, output, result);

// Verify that the output is as expected
   Print("Error Test: ", output);
  }

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

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

// Check if the start index is correct
   Print("Índice de início após BOM: ", startIndex); // Expected: 3
  }

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


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
// Run HttpResponseProcessor tests
   RunTests();

// Create the HttpRequest class instance
   HttpRequest httpRequest("", 5000);

// Variables to store the output
   string output;

// Permorm the GET request
   int responseCode = httpRequest.Request("GET", output, "https://jsonplaceholder.typicode.com/posts/1");

// Show the result
   Print("Response Code: ", responseCode);
   Print("Output: ", output);


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

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

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


Заключение

Мы подошли к концу данной статьи, в которой рассмотрели трансформацию «традиционного» проекта в объектно-ориентированный. Переход от процедурной структуры кода к архитектуре, основанной на классах, не только обеспечивает более четкую организацию, но и облегчает обслуживание и расширение кода.

Актуальность ООП в MQL5:

  • Принятие парадигмы ООП представляет собой значительный качественный скачок в разработке программного обеспечения. В нашем контексте, где MQL5 в основном используется для алгоритмической торговли и автоматизации стратегий на финансовом рынке, важность хорошо структурированного и модульного кода еще значительнее.

Преимущества модульности и инкапсуляции:

  • Организация кода в классы позволяет нам инкапсулировать конкретные функциональные возможности, чтобы сделать систему более интуитивной и простой в обслуживании. Каждый класс становится модулем с определенными обязанностями, и это упрощает выявление и решение проблем, а также способствует расширению системы за счет новых функциональных возможностей.

Преимущества повторного использования кода:

  • ООП поощряет повторное использование кода. Создавая четко определенные классы, можно повторно использовать эти структуры в разных частях проекта или даже в других проектах. Это не только экономит время, но и повышает согласованность и надежность кода.

Простота обслуживания и масштабируемость:

  • Поддержка и масштабирование проекта становятся гораздо более жизнеспособными при ООП. По мере роста вашего проекта или адаптации к новым требованиям возможность изменять конкретный компонент, не затрагивая остальные элементы системы, является неоценимым преимуществом.

Я призываю всех читателей, независимо от уровня их опыта программирования, применять концепции ООП в своих MQL5-проектах. Поначалу переход на ООП может показаться сложным, но долгосрочные преимущества в плане качества кода, эффективности разработки и удобства обслуживания неоспоримы.

Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/13863

Разработка системы репликации (Часть 41): Начало второй фазы (II) Разработка системы репликации (Часть 41): Начало второй фазы (II)
Если до этого момента вам всё казалось правильным, это значит, что вы на самом деле не задумываетесь о долгосрочной перспективе. Когда вы начинаете разрабатывать приложения, а со временем вам больше не приходится создавать новые приложения. Остается только добиться того, чтобы они работали вместе. Давайте рассмотрим, как завершить сборку указателя мыши.
Нейросети это просто (Часть 95): Снижение потребления памяти в моделях Transformer Нейросети это просто (Часть 95): Снижение потребления памяти в моделях Transformer
Модели на основе архитектуры Transformer демонстрируют высокую эффективность, однако их использование осложняется большими затратами ресурсов как на этапе обучения, так и в процессе эксплуатации. В этой статье я предлагаю познакомиться с алгоритмами, которые позволяют уменьшить использование памяти такими моделями.
Нейросети это просто (Часть 96): Многоуровневое извлечение признаков (MSFformer) Нейросети это просто (Часть 96): Многоуровневое извлечение признаков (MSFformer)
Эффективное извлечение и объединение долгосрочных зависимостей и краткосрочных характеристик остаются важной задачей в анализе временных рядов. Правильное их понимание и интеграция необходимы для создания точных и надежных предсказательных моделей.
Разработка системы репликации (Часть 40): Начало второй фазы (I) Разработка системы репликации (Часть 40): Начало второй фазы (I)
Сегодня поговорим о новой фазе системы репликации/моделирования. На данном этапе разговор станет поистине интересным, а содержанием довольно насыщенным. Я настоятельно рекомендую вам внимательно прочитать статью и пользоваться приведенными в ней ссылками. Это поможет вам лучше понять содержание.