![Как разработать агент обучения с подкреплением на MQL5 с интеграцией RestAPI (Часть 4): Организация функций в классах в MQL5](https://c.mql5.com/2/64/Desenvolvendo_um_agente_de_Aprendizado_por_Reforso_em_MQL5_com_Integraddo_RestAPI_gParte_4e_Organiza.jpg)
Как разработать агент обучения с подкреплением на MQL5 с интеграцией RestAPI (Часть 4): Организация функций в классах в MQL5
Введение
Добро пожаловать в четвертую часть нашей серии, в которой мы исследуем создание агента обучения с подкреплением на MQL5 с интеграцией RestAPI. До сегодняшней статьи мы рассматривали такие важные аспекты, как использование RestAPI в MQL5, создание MQL5-функций для взаимодействия с REST API в крестики-нолики, а также выполнение автоматических ходов и тестовых скриптов. Это дало нам прочную основу и помогло понять, как MQL5 взаимодействует с внешними элементами.
В этой статье мы сделаем важный шаг и организуем наши функции в классы в MQL5. Для этого, мы будем использовать объектно-ориентированное программирование, которое представляет собой способ написания кода, помогающий сохранить его организованность и простоту понимания. Это важно, поскольку облегчает нам обслуживание и улучшение кода. Хорошо организованный и модульный код мы сможем использовать в разных частях проекта или даже в будущих проектах.
В оставшейся части статьи мы расскажем, как реструктурировать существующие MQL5-функции в классы. Мы покажем, как это может сделать код более читабельным и эффективным, и также приведем практические примеры того, как это сделать, показывая как это может облегчить обслуживание и улучшение кода.
Объектно-ориентированное программирование (ООП) - это мощный способ разработки программного обеспечения. В MQL5 использование классов является большим превосходством по сравнению с процедурным методом написания кода. В этой части мы рассмотрим, как улучшить качество нашего проекта с помощью этой характеристики. Давайте рассмотрим четыре важных аспекта:
-
Инкапсуляция и модульность: Классы помогают организовать связанные функции и переменные в одном месте, что упрощает обслуживание и уменьшает количество ошибок.
-
Повторное использование кода: Написав класс, можно использовать его в разных местах, что экономит время и поддерживает согласованность кода.
-
Простота обслуживания и улучшения: Если функции разделены на классы, проще найти и исправить ошибки или внести улучшения, так как четкая структура делает код более доступным.
-
Абстракция и гибкость: Классы способствуют абстрагированию, скрывая сложность и раскрывая только то, что нам необходимо. Это делает код более интуитивным и гибким.
Мы покажем, что перестановка функций в классы в 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; } //+------------------------------------------------------------------+
Вызовы и проблемы данного подхода:
-
Отсутствие инкапсуляции и модульности: В настоящее время функции расположены изолированно, без четкого механизма для их группировки по функциональности или назначению. В результате получается код с низкой сплоченностью и высокой связностью, что усложняет обслуживание и понимание логического потока.
-
Ограниченное повторное использование кода: Поскольку функции специфичны и не организованы в модульную структуру, повторное использование кода в различных контекстах или проектах будет ограничено, что может привести к дублированию кода, что увеличивает риск возникновения несоответствий и ошибок.
-
Сложное обслуживание и расширяемость: Без четкого разделения обязанностей, выявление и исправление ошибок, а также добавление новых функциональных возможностей превращается в сложные задачи. Особенно проблемным это становится для проектов, которые расширяются или требуют постоянного обновления.
Примеры текущей организации функций:
В текущем их состоянии функции выполняются по процедурной схеме. Например, функция 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; }
Обратите внимание, что функции содержат несколько повторяющихся элементов, что делает сложным, например, обработку ответов на разные ошибки, так как их можно применить в одной части и игнорировать в другой, или похожие ситуации.
Данный подход, хотя и функционален, но он не использует преимущества объектной ориентации, такие как инкапсуляция и модульность. Каждая функция работает относительно независимо, без единой структуры, которая бы связывала их или управляла их поведением согласованно.
Важность ООП
ООП - это парадигма программирования, которая использует "объекты" в качестве фундаментальных строительных блоков. Эти объекты представляют собой структуры данных, состоящие из полей данных и процедур, называемых методами, которые представляют сущности или концепции реального мира. В ООП каждый объект имеет возможность принимать и отправлять сообщения, а также обрабатывать данные, действуя при этом в качестве автономной единицы с определенными функциями или обязанностями в рамках программной системы.
Преимущества ООП в обслуживании и масштабировании проектов:
-
Упрощенное обслуживание: ООП облегчает обслуживание программного обеспечения благодаря модульной конструкции. Каждый объект - это независимая единица со своей логикой и данными, а это значит, что изменения в конкретном объекте, как правило, не влияют на другие объекты. Эта характеристика делает процесс обновления, исправления ошибок и улучшения системы гораздо более управляемым.
-
Улучшенная масштабируемость: ООП позволяет разработчикам создавать системы, которые можно легко масштабировать по размеру и сложности. Добавление новых функциональных возможностей становится более эффективным, так как новые объекты могут быть созданы с определенными функциями без необходимости обширной модификации существующего кода.
-
Повторное использование кода: Наследование, один из основных принципов ООП, позволяет разработчикам создавать новые классы на основе существующих. Это способствует повторному использованию кода, уменьшает избыточность и облегчает обслуживание.
Как модульность помогает улучшить наш код?
Модульность - одно из главных преимуществ ООП. Она предоставляет разработчикам следующие возможности:
-
Разделение сложных систем: С помощью ООП сложную систему можно разбить на более мелкие, управляемые компоненты (объекты), каждый из которых имеет четко определенные обязанности. Это облегчает понимание, разработку и обслуживание системы.
-
Акцент на абстракцию: Модульность позволяет разработчикам сосредоточиться на абстракции, работать с высокоуровневыми понятиями, а не с низкоуровневыми деталями, а это облегчает решение сложных проблем и делает код более ясным.
-
Поощрение гибкости и расширяемости: Объекты и классы можно разработать так, чтобы они были гибкими и расширяемыми, чтобы позволить системе развиваться и адаптироваться со временем без необходимости полного переписывания.
-
Поощрение сотрудничества: В среде совместной разработки разные команды или разработчики могут одновременно работать над отличающимися модулями или объектами, что повышает эффективность и сокращает время разработки.
Применение ООП в нашем проекте с интеграцией 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:
-
HttpRequestBase(string headers = "", int timeout = 5000): Это конструктор класса HttpRequestBase. Он принимает два необязательных параметра, headers и timeout, которые указывают HTTP-заголовки для отправки в запросах и таймаут для ответа, соответственно. Конструктор инициализирует данные значения и создает экземпляр класса HttpResponseProcessor (класс, который обрабатывает HTTP-ответы).
-
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.
-
virtual int ValidateMethod(string method): Этот метод проверяет, действителен ли указанный метод HTTP (GET или POST). Возвращает true, если он действителен, и возвращает false в обратном случае.
-
virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param): Этот абстрактный виртуальный метод должен быть реализован производными классами. Выполняет HTTP GET-запрос к указанному URL и возвращает данные ответа в параметре data, результат в параметре result и заголовки ответа в result_headers.
-
virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload): Этот абстрактный виртуальный метод должен быть реализован производными классами. Выполняет HTTP POST-запрос к указанному URL с телом запроса (payload) и возвращает данные ответа в параметре data, результат в параметре result и заголовки ответа в result_headers.
-
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:
-
HttpRequest(string headers = "", int timeout = 5000): Это конструктор класса HttpRequest. Вызывает конструктор базового класса HttpRequestBase для того, чтобы запускать параметры заголовка и таймаута.
-
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.
-
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.
-
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)](https://c.mql5.com/2/65/Desenvolvendo_um_sistema_de_Replay_4Parte_41g____LOGO.png)
![Нейросети это просто (Часть 95): Снижение потребления памяти в моделях Transformer](https://c.mql5.com/2/81/Neural_networks_are_easy_Part_95_LOGO.png)
![Нейросети это просто (Часть 96): Многоуровневое извлечение признаков (MSFformer)](https://c.mql5.com/2/82/Neural_networks_are_easy_Part_96__LOGO.png)
![Разработка системы репликации (Часть 40): Начало второй фазы (I)](https://c.mql5.com/2/64/Neural_networks_made_easy_sPart_727_Predicting_trajectories_in_the_presence_of_noise_LOGO__UPSACALE.png)
![MQL5 - Язык торговых стратегий для клиентского терминала MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования