Rest APIを統合したMQL5強化学習エージェントの開発(第4回):MQL5でクラス内の関数を整理する
はじめに
Rest APIを統合したMQL5で強化学習エージェントを作成する連載の第4部へようこそ。これまでに、MQL5でRest APIを使用すること、五目並べでREST APIと対話するためのMQL5関数を作成すること、自動手やテストスクリプトを実行することなどの重要な側面について見てきました。これは私たちに確かな基礎を与え、MQL5が外部要素とどのように相互作用するかを理解するのに役立ちました。
この記事では、重要なステップを踏み、MQL5で関数をクラスに整理します。そのために、オブジェクト指向プログラミング(OOP)を使用します。OOPとは、コードを整理して理解しやすくするための書き方です。これは、私たちがコードを維持し、改善しやすくするために重要です。そのコードはよく整理されており、モジュール化されているため、プロジェクトのさまざまな部分で、あるいは将来のプロジェクトでも使用することができます。
またこの記事では、既存のMQL5関数をクラスに再構築する方法を紹介します。これによって、コードがより読みやすく、効率的になることを確認しましょう。また、この記事にはその実践例が掲載されており、紹介されているアイデアを適用することで、コードのメンテナンスや改善がいかに容易になるかが示されています。
オブジェクト指向プログラミング(OOP)は、ソフトウェア開発の強力な方法です。MQL5では、クラスの使用は手続き的なコードの書き方に比べて大きな利点があります。この部分では、この特性を利用してプロジェクトの質を向上させる方法を見ていきます。4つの重要な側面を見てみましょう。
-
カプセル化とモジュール化:クラスは、関連する関数や変数を一箇所にまとめるのに役立ち、メンテナンスが容易になり、エラーを減らすことができます。
-
コードの再利用:一度クラスを書けば、さまざまな場所でそのクラスを使用することができ、時間の節約とコードの一貫性の維持が可能になります。
-
メンテナンスと改善のしやすさ:関数がクラスに分離されていると、明確な構造によってコードがよりアクセスしやすくなるため、バグを発見して修正したり、改良を加えたりするのが容易になります。
-
抽象化と柔軟性:クラスは複雑さを隠し、必要なものだけを明らかにすることで、抽象化を促進します。これにより、コードがより直感的で柔軟なものになります。
MQL5で関数をクラスに並べ替えるのは、単に美しさのためではなく、コードをより効率的に、より理解しやすく、よりメンテナンスしやすくする重要な変更であることがわかるでしょう。この記事では、孤立した関数を明確に定義されたクラスメソッドに変換する方法を紹介します。これは現在のプロジェクトを向上させるだけでなく、将来のMQL5プロジェクトのための強固な土台作りにも役立つでしょう。
現在のコードの状態
現在の状態では、私たちのコードはSendGetRequest、 SendPostRequest、RequestといったHTTPリクエストを処理するための関数で構成されています。これらの関数は、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の基本原則の1つである継承によって、開発者は既存のクラスをベースに新しいクラスを作ることができます。これにより、コードの再利用が促進され、冗長性が減り、メンテナンスが容易になります。
モジュール化はコードの改善にどのように役立つのでしょうか。
モジュール化は、OOPの主な利点の1つです。開発者には以下の機能が提供されます。
-
複雑なシステムを分割する:OOPを使えば、複雑なシステムをより小さく、管理しやすいコンポーネント(オブジェクト)に分解することができ、それぞれが明確に定義された責任を持ちます。これにより、システムの理解、開発、メンテナンスが容易になります。
-
抽象化を重視する:モジュール化によって、開発者は抽象化に集中し、低レベルの詳細よりも高レベルの概念に取り組むことができます。複雑な問題を簡単に解決し、コードをよりクリーンにできます。
-
柔軟性と拡張性の奨励:オブジェクトとクラスは、柔軟で拡張可能なように設計することができるため、システムを完全に書き直す必要なく、時間の経過とともに進化し、適応していくことができます。
-
コラボレーションの奨励:共同開発環境では、異なるチームや開発者が異なるモジュールやオブジェクトを同時に作業できるため、効率が向上し、開発時間を短縮できます。
Rest APIを統合したプロジェクトでOOPを使用することにより、ソフトウェアの複雑性を管理するための強固なアプローチが提供され、メンテナンス性、拡張性、およびコード全体の品質が大幅に向上します。
クラス内の関数のリファクタリング
OOPの重要性と、それがプロジェクトのメンテナンス性と拡張性をいかに向上させるかを理解したところで、既存の関数をクラスにリファクタリングすることを提案します。このプロセスをよりよく説明するために、新しいオブジェクト指向コードがどのように整理され、理解しやすくなるかを示す図を提供しましょう。手続き的なコードを、より整理され理解しやすいオブジェクト指向のコードに変換するために、段階的なプロセスを踏んでいきます。
実装
手順1 - インターフェイスの定義:まずは、オブジェクトが持つべきメソッドや機能を記述するインターフェイスを定義することから始めましょう。これには、IHttpRequestとIHttpResponseProcessorの2つあります。これらのインターフェイスは、具象クラスが従わなければならない契約を定義しています。
//+------------------------------------------------------------------+ //| 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という2つのオプションのパラメータをとり、それぞれリクエストで送信する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):この抽象仮想メソッドは、派生クラスによって実装されなければなりません。指定されたURLにリクエストボディ(payload)を指定して HTTP POSTリクエストを実行し、dataパラメータにレスポンスデータ、resultパラメータに結果、result_headersにレスポンスヘッダーを返します。
-
virtual int ProcessResponse(int res, string &out, uchar &result[]):この抽象仮想メソッドは、派生クラスによって実装されなければなりません。resレスポンスコードに基づいてHTTPレスポンスを処理します。レスポンスが成功した場合(レスポンスコードは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) // 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のコンストラクタを呼び出します。
-
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メソッドの実装です。リクエストボディ(payload)を含む、指定されたURLへのHTTP POSTリクエストを実行します。生のレスポンスデータは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レスポンス処理ロジックなど)1つのコード部分への変更は、コード全体に散在する複数の関数を変更することなく、1箇所でおこなうことができます。
- 拡張性:オブジェクト指向の構造は、拡張性と柔軟性を持つように設計されているため、新しい機能を追加したり、新しい要件にコードを適合させたりするのは簡単です。
- スケーラビリティ:プロジェクトが成長するにつれて、新機能の追加や他の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クラスのprocessorオブジェクトを作成します。
- 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クラスを使用して、成功、エラー、BOMの存在など、HTTPレスポンスのさまざまな側面を処理する方法を示しています。また、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() { //--- } //+------------------------------------------------------------------+
結論
「伝統的な」プロジェクトがオブジェクト指向のプロジェクトへと変貌を遂げる様子を見てきたこの記事もこれで終わりです。手続き的なコード構造からクラスベースのアーキテクチャに移行することで、よりすっきりとした構成になるだけでなく、コードのメンテナンスや拡張が容易になります。
MQL5におけるOOPの妥当性:
- OOPパラダイムの採用は、ソフトウェア開発における大きな飛躍を意味します。MQL5が主にアルゴリズム取引や金融市場戦略の自動化に使用される文脈では、十分に構造化されモジュール化されたコードの重要性はさらに大きくなります。
モジュール化とカプセル化の利点:
- コードをクラスにまとめることで、特定の機能をカプセル化し、システムをより直感的でメンテナンスしやすいものにすることができます。各クラスは特定の責任を持つモジュールとなり、問題の特定と解決、新機能によるシステムの拡張が容易になります。
コード再利用の利点:
- OOPはコードの再利用を促進します。よく定義されたクラスを作成することで、プロジェクトのさまざまな部分で、あるいは他のプロジェクトでも、これらの構造を再利用することができます。これは時間を節約するだけでなく、コードの一貫性と信頼性を向上させます。
メンテナンスの容易さと拡張性:
- プロジェクトの維持と拡張は、OOPによってはるかに実行可能になります。プロジェクトが成長したり、新しい要件に対応したりする際に、システムの他の部分に影響を与えることなく特定のコンポーネントを変更できることは、非常に貴重な利点です。
プログラミング経験のレベルにかかわらず、すべての読者にMQL5プロジェクトにOOPの概念を適用することをお勧めします。OOPへの切り替えは、最初は難しいように思えるかもしれませんが、コードの品質、開発効率、メンテナンス性の面で長期的なメリットがあることは紛れありません。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/13863
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索