开发具有 RestAPI 集成的 MQL5 强化学习代理(第 4 部分):在 MQL5 中组织类中的函数
概述
欢迎阅读我们系列文章的第四部分,在本系列文章中,我们在探讨如何在 MQL5 中创建一个与 RestAPI 集成的强化学习代理。在今天的文章之前,我们了解了在 MQL5 中使用 RestAPI、创建 MQL5 函数与 REST API 进行井字游戏交互以及执行自动移动和测试脚本等重要方面。这为我们奠定了坚实的基础,并帮助我们了解 MQL5 如何与外部元素交互。
在本文中,我们将迈出重要的一步,在 MQL5 中将函数组织成类。为此,我们将使用面向对象编程(object-oriented programming,OOP)。OOP 是一种编写代码的方法,有助于保持代码的组织性和易于理解。这很重要,因为它使我们更容易维护和改进代码。它的代码组织良好,是模块化的,我们可以在项目的不同部分甚至在未来的项目中使用它。
在本文中,我们还将了解如何将现有的 MQL5 函数重组到类中。我们将看到这如何使代码更可读、更高效。此外,文章还包含了如何做到这一点的实际例子,展示了如何应用所介绍的思路使代码更易于维护和改进。
面向对象编程(OOP)是一种强大的软件开发方法。在MQL5中,类的使用比过程式代码编写方法具有很大的优势。在本部分中,我们将探讨如何利用这一特点提高项目质量。让我们来看看四个重要方面:
-
封装和模块化:类有助于将相关函数和变量组织在一处,使其更易于维护并减少错误。
-
代码重复使用:一旦编写了一个类,就可以在不同的地方使用它,从而节省时间并保持代码的一致性。
-
易于维护和改进:当函数被分到类中时,更容易发现和修复错误或进行改进,因为清晰的结构使代码更容易访问。
-
抽象性和灵活性:类可以隐藏复杂性,只显示我们需要的东西,从而促进抽象化。这使得代码更加直观和灵活。
我们将看到,在 MQL5 中将函数重新组织为类并不只是为了美观,而是一个重大的改变,它使代码更高效、更易于理解和维护。本文将介绍如何将孤立的函数转化为明确定义的类方法,这将带来直接和长期的好处。这不仅将改进我们当前的项目,还将帮助我们为未来的 MQL5 项目奠定坚实的基础。
当前代码状态
在当前状态下,我们的代码包含了好几个处理 HTTP 请求的函数,如SendGetRequest、 SendPostRequest 和 Request。这些函数负责向 API 发送 GET 和 POST 请求,处理响应并消除可能出现的错误。
//+------------------------------------------------------------------+ //| Request.mqh | //| Copyright 2023, Lejjo Digital | //| https://www.mql5.com/en/users/14134597 | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, Lejjo Digital" #property link "https://www.mql5.com/en/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 函数的作用是根据我们指定的 HTTP 方法,方便调用 GET 和 POST 函数。
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; }
需要注意的是,这些函数包含多个重复元素,这就很难处理对不同错误的响应,因为它们可能在一个部分应用,而在另一个部分被忽略,或者出现类似情况。
这种方法虽然功能强大,但并没有利用面向对象的好处,如封装和模块化。每个函数都相对独立地运行,没有一个单一的结构将它们联系起来或以一致的方式控制它们的行为。
OOP 的重要性
OOP 是一种使用 "对象" 作为基本构件的编程范式。这些对象是由数据字段和称为方法的过程组成的数据结构,代表现实世界中的实体或概念。在 OOP 中,每个对象都具有接收和发送信息以及处理数据的能力,同时充当软件系统中具有特定功能或职责的自治单元。
OOP 在维护和扩展项目方面的优势:
-
更易于维护:由于采用模块化设计,OOP 使软件更易于维护。每个对象都是一个独立的单元,有自己的逻辑和数据,这意味着对某个对象的更改一般不会影响其他对象。这一功能使更新、修复错误和改进系统的过程更加易于管理。
-
可扩展性提高:OOP 允许开发人员创建可以轻松扩展大小和复杂性的系统。添加新功能变得更加高效,因为可以创建具有特定功能的新对象,而不需要对现有代码进行大量修改。
-
代码重复使用:继承是 OOP 的核心原则之一,它允许开发人员在现有类的基础上创建新类。这促进了代码重用,减少了冗余,并使维护更容易。
模块化如何帮助改进我们的代码?
模块化是 OOP 的主要优势之一。它为开发人员提供了以下特性:
-
分解复杂系统:使用 OOP,可以将复杂的系统分解为更小、可管理的组件(对象),每个组件都有明确的职责。这使得系统更易于理解、开发和维护。
-
专注于抽象:模块化使开发人员能够专注于抽象,研究高层次的概念而不是低层次的细节,从而使复杂问题更容易解决,代码更简洁。
-
鼓励灵活性和可扩展性:对象和类可以被设计为灵活和可扩展的,以允许系统随着时间的推移而发展和适应,而不需要完全重写。
-
鼓励协作:在协作开发环境中,不同的团队或开发人员可以同时处理不同的模块或对象,从而提高效率并缩短开发时间。
在我们的 RestAPI 集成项目中使用 OOP 提供了一种管理软件复杂性的稳健方法,大大提高了可维护性、可扩展性和整体代码质量。
重构类中的函数
现在既然我们已经了解了 OOP 的重要性以及它如何提高项目的可维护性和可扩展性,我建议将现有函数重构为类。为了更好地说明这一过程,我们将提供一张图表,展示新的面向对象代码将如何更有条理、更易懂。我们将按照一个循序渐进的过程,将过程代码转化为更有条理、更易于理解的面向对象代码。
实现
第一步,定义接口。首先,让我们为对象定义接口,这些接口将描述对象应具有的方法和功能。我们有两个接口: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; };
第二步,创建抽象类。我们创建实现这些接口的抽象类。这些类没有方法的实际实现,而是定义了相关的结构。抽象类是 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):派生类必须实现这个抽象虚拟方法。向指定 URL 执行 HTTP GET 请求,并在 data 参数中返回响应数据,在 result 参数中返回结果,在 result_headers 中返回响应报头信息。
-
virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload):派生类必须实现这个抽象虚拟方法。使用请求体(payload)向指定 URL 执行 HTTP POST 请求,并在 data 参数中返回响应数据,在 result 参数中返回结果,在 result_headers 中返回响应报头信息。
-
virtual int ProcessResponse(int res, string &out, uchar &result[]):派生类必须实现这个抽象虚拟方法。它根据 "res" 响应代码处理 HTTP 响应。如果响应成功(响应代码范围在 200 到 299 之间),则调用 ProcessSuccessResponse。否则将调用 ProcessErrorResponse。结果保存在 out 中,原始响应数据保存在 result 中。
第三步,创建具体的类。让我们创建实现接口方法的具体类。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; };
第四步,实现具体类的方法。让我们来实现具有实际功能的具体类方法。这里有 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) // Errors 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 的构造函数,以触发 header 和 timeout 参数。
-
virtual int PerformGetRequest(char &data[], uchar &result[], string &result_headers, const string url, const string query_param):这是 HttpRequest 类中 PerformGetRequest 方法的实现。向指定 URL 执行 HTTP GET 请求,包括请求参数(如有)。原始响应数据将保存在 data 中,结果将保存在 result 中,响应报头将保存在 result_headers 中。
-
virtual int PerformPostRequest(char &data[], uchar &result[], string &result_headers, const string url, const string payload):这是 HttpRequest 类中 PerformPostRequest 方法的实现。向指定 URL 执行 HTTP POST 请求,包括请求正文(payload)。我们将原始响应数据保存在 data 中,将结果保存在 result 中,将响应报头保存在 result_headers 中。
-
virtual int ProcessResponse(int res, string &out, uchar &result[]):HttpRequest 类中 ProcessResponse 方法的实现。如果响应成功(响应代码在 200 和 299 之间),则调用 ProcessSuccessResponse,否则调用 ProcessErrorResponse。我们将在 "out" 中存储结果,在 "result" 中存储原始响应数据。
重构的好处:
重构我们项目的代码,从面向过程的方法转变为面向对象的方法,可以带来几个显著的好处。我们将通过比较旧代码和使用类的新代码来讨论它们,并重点讨论这如何提高代码的可读性、可维护性和适应性。
旧代码与类的新代码的比较:
先前的代码(过程性):
- 结构:代码由处理 HTTP 请求各个方面的独立函数(SendGetRequest、SendPostRequest、Request)组成。
- 维护:对一个函数的任何改动都可能需要对其他函数进行类似的改动,因为代码是重复的,并不能有效地共享共同的逻辑。
- 可读性:虽然每个函数都相对简单,但整个代码却更难理解,尤其是对于新开发人员来说。
新的(面向对象)代码:
- 结构:引入接口(IHttpRequest、IHttpResponseProcessor)和抽象类(HttpRequestBase、HttpResponseProcessorBase),然后是具体实现(HttpRequest、HttpResponseProcessor)。
- 维护:现在的代码更加模块化,每个类都有明确的任务。这使得更新和修复代码变得更加容易,因为对一个类的更改通常不会影响其他类。
- 可读性:组织成类和方法使代码更加直观。所有的类和方法都有一个明确的目的,使人们更容易理解代码的作用以及它是如何工作的。
提高了可读性和维护性:
可读性
- 逻辑组织:现在,代码被划分为具有特定功能的类,从而更容易理解代码不同部分之间的关系。
- 描述性名称:在使用类和方法时,名称可以更具描述性,以清楚地传达每段代码的功能。
维护
- 易于更新:对一段代码(如 HTTP 响应处理逻辑)的修改可以在一个地方完成,而不必修改分散在整个代码中的多个函数。
- 可扩展性:添加新功能或使代码适应新需求很容易,因为面向对象的结构被设计为可扩展和灵活的。
- 可扩展性:随着项目的发展,添加新功能或与其他 API 和系统集成变得更加容易。类可以扩展,也可以在现有类的基础上创建新类。
- 代码重复使用:组件可以在项目的不同部分甚至其他项目中重复使用,节省时间和精力。
- 易于测试:代码测试变得更容易,因为你可以单独关注特定单元(类或方法)。
将我们的代码重构为面向对象的方法是一项战略性变革,它不仅提高了项目当前的质量,还为项目的未来发展奠定了坚实的基础。这种转换为我们提供了更清晰的代码,更容易理解、维护和扩展。
通过将逻辑封装到定义良好的类中,我们减少了冗余,提高了清晰度,并提高了代码的效率。在不断变化的环境中,灵活性和快速响应新要求的能力尤为重要。
此外,OOP 实现的模块化有助于团队协作,可以同时处理项目的不同部分,减少代码冲突的风险。这也为更先进的开发技术打开了大门,例如单元测试,这些技术更容易在面向对象的框架中实现。
//+------------------------------------------------------------------+ //| 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) // Errors 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; }; //+------------------------------------------------------------------+
使用类的示例
本节将提供使用创建的类在 MQL5 中执行 HTTP 请求的实际示例。这些例子说明了代码的重复使用和创建新功能的效率。
检查响应是否成功:
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'};:创建一个数组,其UTF-8 BOM 后面是 '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://..."):向指定 URL 执行 GET 请求,并将响应保存到输出变量中。
这些示例展示了如何使用 HttpResponseProcessor 和 HttpRequest 类来处理 HTTP 响应的各个方面,如成功、错误和是否存在 BOM。它们还演示了如何使用 HttpRequest 类简单发出 GET 请求。
将代码模块化为类是编程中的一种基本方法,它可以创建一个有组织且易于理解的系统。这种做法是将代码划分为称为类的独立单元,每个类都有自己的职责和功能。
使用这种技术,开发人员可以更有逻辑、更清晰地构建代码,使其更易读、更容易理解。这意味着,我们处理的是无序的代码,而不是单片代码;开发人员使用系统的小部分,每个部分都由一个类表示。
这种方法的优点是,您可以整体设计类,将相关的方法和属性组合在一起。这不仅使代码更容易理解,而且更容易维护和进一步开发,因为更容易在单个代码块中发现和修复问题。
此外,类模块化促进了代码重用,因为类可以在程序中的不同位置使用,从而节省了创建类似功能的时间和精力。
下面是一个包含测试代码的完整示例,用于演示 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 (example with error 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; // 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); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
结论
本文到此结束,我们已经了解了从 "传统" 项目到面向对象项目的转变。从过程代码结构转向基于类的架构,不仅能提供更简洁的组织结构,还能使代码更易于维护和扩展。
OOP 在 MQL5 中的相关性:
- OOP 范式的采用代表了软件开发的重大飞跃。在我们的背景下,MQL5主要用于算法交易和金融市场策略的自动化,结构良好和模块化代码的重要性更大。
模块化和封装的好处:
- 将代码组织成类使我们能够封装特定的功能,使系统更直观、更易于维护。每个类都成为一个具有特定职责的模块,使识别和解决问题以及用新功能扩展系统变得更加容易。
代码重用的好处:
- OOP 促进了代码的重复使用。通过创建定义良好的类,您可以在项目的不同部分甚至其他项目中重用这些结构。这不仅节省了时间,还提高了代码的一致性和可靠性。
易于维护和扩展:
- 通过 OOP,维护和扩展项目变得更加可行。随着项目的发展或适应新的需求,在不影响系统其他部分的情况下更改特定组件的能力是一个宝贵的优势。
我鼓励所有读者,无论其编程经验水平如何,都能在自己的 MQL5 项目中应用 OOP 概念。改用 OOP 一开始似乎很有挑战性,但从代码质量、开发效率和可维护性方面来看,其长期优势是不可否认的。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/13863