
Connexus Observer(第8回):リクエストObserverの追加
はじめに
本記事は、Connexusというライブラリを構築する連載の続編です。第1回では、WebRequest関数の基本的な動作を理解し、その各パラメータについて説明しました。また、この関数の使用方法や課題を示すサンプルコードも作成しました。前回の記事では、リクエストの送信を担当し、リクエストオブジェクト(CHttpRequest)を受け取り、ステータスコード・処理時間・本文・レスポンスヘッダーなどの情報を含むレスポンス(CHttpResponse)を返す、シンプルで直感的なクライアント層を実装しました。さらに、WebRequest関数との依存を解消し、ライブラリの柔軟性を高めるために、CHttpTransportという新たな層を導入しました。
本連載の第8回となる本記事では、クライアントが複数のリクエストを管理しやすくするために、Observerパターンをライブラリに実装します。それでは、さっそく始めましょう。
ライブラリの現在の状態を振り返るため、以下に最新のクラス図を示します。
Observerについて
Observerを、遠くから静かに見守り、すべてに注意を払いながらも決して干渉しない友人だと想像してください。プログラミングの世界でも、Observerパターンはこれとよく似たことをおこないます。つまり、特定のオブジェクトが何かしらの変化を検知し、それを必要とする他のオブジェクトに「通知」する仕組みですが、通知される側は「誰が」変化を引き起こしたのかを知る必要はありません。まるで魔法のように、誰かが何かを動かせば、必要な人にその情報が自動的に伝わるのです。このパターンは、ビジネスロジックとユーザーインターフェイスの動作をうまく連携させるための代表的なデザインパターンの一つです。このようにして、システムはよりスムーズに動作し、さまざまなコンポーネントがイベントに応じて自動的に適応するようになります。
Observerパターンの発想は、厳密に結びついたオブジェクト同士が互いに依存し、「身動きが取れない」ような堅苦しいシステムにおける厄介な問題を解決するために生まれました。解決策は、依存関係を断ち切り、システムを軽量化することです。Observerという名前が付けられる前から、プログラマーたちはすでにシステムをより軽く、柔軟にする方法を模索していました。そして1994年、エーリヒ・ガンマ、リチャード・ヘルム、ラルフ・ジョンソン(英語)、そしてジョン・ブリシディースの4人が著書「Design Patterns:Elements of Reusable Object-Oriented Software」(1994年)の中でObserverパターンを正式に定義しました。彼らは、オブジェクト間の強い結びつきを避けながら、単一のオブジェクトの変更に応じて複数のオブジェクトを自動的に更新できる理想的な方法として、このパターンを提案したのです。
Observerパターンを使用する理由
Observerパターンは、オブジェクト同士の結びつきを弱め、より独立した設計にしたいときに最適です。Subjectは「誰が見ているか」を知る必要はなく、ただ「変化があった」と知らせるだけで、あとは自由に動くことができます。また、リアルタイム更新が求められる場面でも非常に役立ちます。たとえば、インタラクティブなUIや自動通知など、即座に更新が必要なシステムでは、Observerパターンを導入することでより俊敏に動作させることができます。
Observerパターンのコンポーネント
- Subject:状態が変化し、その変化をObserverに通知する役割を持つ「中心的な存在」です。Observerのリストを管理し、リストへの追加や削除を行うメソッドを備えています。
- Observer:各Observerは「リスナー」のような存在で、常にSubjectの変化に応じて動作する準備ができています。Observerは、Subjectが変更を検知した際に呼び出すupdateメソッドを実装します。
以下に、Observerパターンがどのように機能するかを示す図を追加します。
- 主なクラス
- Subject:このクラスは、Observerのコレクション(observerCollection)を維持し、これらのObserverを管理するためのメソッドを提供します。その機能は、状態の変化が発生するたびにObserverに通知することです。
- メソッド
- registerObserver(observer):コレクションにObserverを追加します。
- unregisterObserver(observer):コレクションからObserverを削除します。
- notifyObservers():observerCollection内の各Observerのupdate()メソッドを呼び出して、すべてのObserverに通知します。
- メソッド
- Observer:update()メソッドを定義するインターフェイスまたは抽象クラスです。すべての具体的なObserverクラス(Observerの具象)は、Subjectが変更を通知するときに呼び出されるこのメソッドを実装する必要があります。
- Subject:このクラスは、Observerのコレクション(observerCollection)を維持し、これらのObserverを管理するためのメソッドを提供します。その機能は、状態の変化が発生するたびにObserverに通知することです。
- 具体的なクラス
- ConcreteObserverAとConcreteObserverB:これらはObserverインターフェイスの具体的な実装です。それぞれがupdate()メソッドを実装し、Subjectの変更に対する特定の応答を定義します。
- SubjectとObserverの関係
- SubjectはObserverのリストを保持し、コレクション内の各Observerに対してobserver.update()を呼び出してObserverに通知します。
- 具体的なObserverは、update()メソッドの特定の実装に従って、Subjectで発生する変更に反応します。
これはConnexusライブラリでどのように役立つのでしょうか。このパターンを使用して、リクエストが送信されたとき、応答が受信されたとき、さらには予期しないエラーが発生したときにクライアントコードに通知します。このパターンを使用すると、クライアントにこれが発生したことが通知されます。これにより、たとえば「エラーが生成された場合はこれを実行する」、「リクエストがおこなわれた場合はこれを実行する」、「応答を受信した場合はこれを実行する」などの条件をコード内に作成する必要がなくなるため、ライブラリが使いやすくなります。
実践的なコード
まず、ライブラリのコンテキストでこのパターンを追加する方法を示した図を示します。
実装がどのようにおこなわれるかをよりよく理解しましょう。
- この図は参照図と同じ構造になっていることに注意してください。
- Observerがアクセスできる2つのメソッドを追加しました。
- OnSend()→リクエストが送信されたとき。
- OnRecv()→応答が取得されたとき。
- IHttpObserverは抽象クラスではなく、インターフェイスになります。
IHttpClientインターフェイスの作成
まず、パス<Connexus/Interface/IHttpClient.mqh>にIHttpClientインターフェイスを作成します。そして、2つの通知関数を定義します。
//+------------------------------------------------------------------+ //| IHttpObserver.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "../Core/HttpRequest.mqh" #include "../Core/HttpResponse.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ interface IHttpObserver { void OnSend(CHttpRequest *request); void OnRecv(CHttpResponse *response); }; //+------------------------------------------------------------------+
CHttpClientでObserverのリストを作成する
インターフェイスのインポートを追加しましょう。
#include "../Interface/IHttpObserver.mqh"次に、クラスのprivateフィールドにObserverの配列を作成します。この配列にはポインタを格納する必要があるため、変数名の前に「*」を追加する必要があることに注意してください。また、配列に格納されているすべてのObserverを追加、削除、通知するためのpublicメソッドも作成します。
//+------------------------------------------------------------------+ //| HttpClient.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "HttpRequest.mqh" #include "HttpResponse.mqh" #include "../Constants/HttpMethod.mqh" #include "../Interface/IHttpTransport.mqh" #include "../Interface/IHttpObserver.mqh" #include "HttpTransport.mqh" //+------------------------------------------------------------------+ //| class : CHttpClient | //| | //| [PROPERTY] | //| Name : CHttpClient | //| Heritage : No heritage | //| Description : Class responsible for linking the request and | //| response object with the transport layer. | //| | //+------------------------------------------------------------------+ class CHttpClient { private: IHttpObserver *m_observers[]; // Array of observers public: //--- Observers void RegisterObserver(IHttpObserver *observer); void UnregisterObserver(IHttpObserver *observer); void OnSendNotifyObservers(CHttpRequest *request); void OnRecvNotifyObservers(CHttpResponse *response); }; //+------------------------------------------------------------------+ //| Add observer pointer to observer list | //+------------------------------------------------------------------+ void CHttpClient::RegisterObserver(IHttpObserver *observer) { int size = ArraySize(m_observers); ArrayResize(m_observers,size+1); m_observers[size] = observer; } //+------------------------------------------------------------------+ //| Remove observer pointer to observer list | //+------------------------------------------------------------------+ void CHttpClient::UnregisterObserver(IHttpObserver *observer) { int size = ArraySize(m_observers); for(int i=0;i<size;i++) { if(GetPointer(m_observers[i]) == GetPointer(observer)) { ArrayRemove(m_observers,i,1); break; } } } //+------------------------------------------------------------------+ //| Notifies observers that a request has been made | //+------------------------------------------------------------------+ void CHttpClient::OnSendNotifyObservers(CHttpRequest *request) { int size = ArraySize(m_observers); for(int i=0;i<size;i++) { m_observers[i].OnSend(request); } } //+------------------------------------------------------------------+ //| Notifies observers that a response has been received | //+------------------------------------------------------------------+ void CHttpClient::OnRecvNotifyObservers(CHttpResponse *response) { int size = ArraySize(m_observers); for(int i=0;i<size;i++) { m_observers[i].OnRecv(response); } } //+------------------------------------------------------------------+最後に、Observerに実際に通知されるように、リクエストを送信する関数内で通知関数を呼び出します。
//+------------------------------------------------------------------+ //| class : CHttpClient | //| | //| [PROPERTY] | //| Name : CHttpClient | //| Heritage : No heritage | //| Description : Class responsible for linking the request and | //| response object with the transport layer. | //| | //+------------------------------------------------------------------+ class CHttpClient { private: IHttpObserver *m_observers[]; // Array of observers public: //--- Basis function bool Send(CHttpRequest &request, CHttpResponse &response); }; //+------------------------------------------------------------------+ //| Basis function | //+------------------------------------------------------------------+ bool CHttpClient::Send(CHttpRequest &request, CHttpResponse &response) { //--- Request uchar body_request[]; request.Body().GetAsBinary(body_request); //--- Response uchar body_response[]; string headers_response; //--- Notify observer of request this.OnSendNotifyObservers(GetPointer(request)); //--- Send ulong start = GetMicrosecondCount(); int status_code = m_transport.Request(request.Method().GetMethodDescription(),request.Url().FullUrl(),request.Header().Serialize(),request.Timeout(),body_request,body_response,headers_response); ulong end = GetMicrosecondCount(); //--- Notify observer of response this.OnRecvNotifyObservers(GetPointer(response)); //--- Add data in Response response.Clear(); response.Duration((end-start)/1000); response.StatusCode() = status_code; response.Body().AddBinary(body_response); response.Header().Parse(headers_response); //--- Return is success return(response.StatusCode().IsSuccess()); } //+------------------------------------------------------------------+
作業は完了しました。コードを書いたときよりも簡単ではなかったでしょうか。これで、ライブラリ内の実装全体が完了しました。Observer、つまりIHttpObserverを実装する具体的なクラスを作成する必要があります。次のトピックであるテストでこれを実行します。
テスト
今必要なのはライブラリを使うことだけです。これをおこなうには、<Experts/Connexus/Tests/TestObserver.mq5>ディレクトリにTestObserver.mq5という新しいテストファイルを作成します。ライブラリをインポートし、OnInit()イベントのみを残します。
//+------------------------------------------------------------------+ //| TestObserver.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include <Connexus/Core/HttpClient.mqh> //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
インポートのすぐ下に、IHttpClientインターフェイスを実装する具体的なクラスを作成します。このクラスは、ライブラリを使用して送受信されたデータをターミナルコンソールに印刷するだけです。
//+------------------------------------------------------------------+ //| TestObserver.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include <Connexus/Core/HttpClient.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CMyObserver : public IHttpObserver { public: CMyObserver(void); ~CMyObserver(void); void OnSend(CHttpRequest *request); void OnRecv(CHttpResponse *response); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CMyObserver::CMyObserver(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CMyObserver::~CMyObserver(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CMyObserver::OnSend(CHttpRequest *request) { Print("-----------------------------------------------"); Print("Order sent notification received in CMyObserver"); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CMyObserver::OnRecv(CHttpResponse *response) { Print("-----------------------------------------------"); Print("Response notification received in CMyObserver"); } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create objects CHttpRequest request; CHttpResponse response; CHttpClient client; CMyObserver *my_observer = new CMyObserver(); //--- Configure request request.Method() = HTTP_METHOD_GET; request.Url().Parse("https://httpbin.org/get"); //--- Adding observer client.RegisterObserver(my_observer); //--- Send client.Send(request,response); //--- Delete pointer delete my_observer; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
これをチャート上で実行すると、次の結果が得られます。
これは、CMyObserverクラスの関数がライブラリ内で呼び出されたことを示しており、これによってすべてが変わり、柔軟性を実現するという主な目的が達成されてライブラリが完成します。
最も興味深いのは、コードのさまざまな部分に複数のObserverを配置できることです。複数のクラスに分割されたEAがある場合、これらの各クラスでIHttpObserverの実装を作成すれば完了です。リクエストが送信されるか、応答が受信されるとすぐに通知されます。
これらのObserverを含めると、ライブラリの現在の図は次のようになります。
フォルダのリファクタリング
現在、すべてのライブラリファイルのディレクトリは次のようになります。
|--- Connexus |--- |--- Constants |--- |--- |--- HttpMethod.mqh |--- |--- |--- HttpStatusCode.mqh |--- |--- Core |--- |--- |--- HttpClient.mqh |--- |--- |--- HttpRequest.mqh |--- |--- |--- HttpResponse.mqh |--- |--- |--- HttpTransport.mqh |--- |--- Data |--- |--- |--- Json.mqh |--- |--- Header |--- |--- |--- HttpBody.mqh |--- |--- |--- HttpHeader.mqh |--- |--- Interface |--- |--- |--- IHttpObserver.mqh |--- |--- |--- IHttpTransport.mqh |--- |--- URL |--- |--- |--- QueryParam.mqh |--- |--- |--- URL.mqh
2つの調整をおこないます。URLフォルダのファイルをDataフォルダに含め、名前をUtilsに変更します。これにより、同様の目的のファイルを含む両方のフォルダが簡素化されます。インターフェイスはライブラリのコアの一部であるため、Coreフォルダ内にインターフェイスフォルダも追加します。最終的に、ライブラリのフォルダ構造は次のようになります。
|--- Connexus |--- |--- Constants |--- |--- |--- HttpMethod.mqh |--- |--- |--- HttpStatusCode.mqh |--- |--- Core |--- |--- |--- Interface |--- |--- |--- |--- IHttpObserver.mqh |--- |--- |--- |--- IHttpTransport.mqh |--- |--- |--- HttpClient.mqh |--- |--- |--- HttpRequest.mqh |--- |--- |--- HttpResponse.mqh |--- |--- |--- HttpTransport.mqh |--- |--- Utils |--- |--- |--- Json.mqh |--- |--- |--- QueryParam.mqh |--- |--- |--- URL.mqh |--- |--- Header |--- |--- |--- HttpBody.mqh |--- |--- |--- HttpHeader.mqh
メソッドの名前変更
理解しやすく、保守しやすく、さらに改善しやすいコードを書くためには、標準的なコーディングスタイルを採用することが重要です。ライブラリを作成する際、一貫した標準を持つことは単なる見た目の問題ではなく、コードの明確さ、予測可能性、そして強固な基盤をもたらします。この統一されたスタイルは、単なる整理整頓ではなく、ライブラリの品質、堅牢性、そして健全な成長への投資です。最初は些細なことに思えるかもしれませんが、最終的にはコードを安全にし、進化しやすくする指針となります。一見すると些細なことに思えるかもしれませんが、最終的にはコードの安全性を高め、スムーズな進化を促す指針となるのです。
標準スタイルを持つことがなぜ重要なのでしょうか。
- 一貫性と読みやすさ:統一されたスタイルで適切に構造化されたコードは、どの開発者にとっても読みやすく、理解しやすいものになります。明確に定義された標準があれば、スタイルのバラつきや矛盾を解読する時間を省き、コードの本質であるロジックに集中できるようになります。スペース、インデント、命名規則などの細かな要素は、より直感的で分かりやすい開発体験を生み出すための重要な要素です。すべてが統一されていることで、コードの可読性が向上し、異なるスタイルが混在することで生じる混乱を減らすことができます。
- メンテナンスと拡張の容易さ:ライブラリは決して時間の中で止まるものではなく、新たな課題が生じるたびに調整が求められます。コーディングスタイルを標準化することで、メンテナンスが容易になり、エラーの発生も減少します。 これにより、問題の修正にかかる時間を削減できるだけでなく、新しい開発者がコードを素早く理解し、効率的に共同作業できるようになります。さらに、最初から適切に構造化されたライブラリは、新しい機能を追加する際も、予測可能で整理された環境のもとでスムーズに拡張できます。
それでは、コード内でいくつかの標準を定義しましょう。主に関数の名前付けに関してです。すでにいくつかの標準が適用されています。
- すべてのクラスは名前の前に「C」という接頭辞を使用します。
- すべてのインターフェイスは名前の前に「I」という接頭辞を使用します。
- private変数は接頭辞「m_」を使用します。
- メソッドは常に大文字で始まる必要があります。
- ENUM値は大文字で記述する必要があります。
これらの標準はすべて開発中にライブラリにすでに適用されていますが、次のような他の標準も追加される予定です。
- クラスの属性を設定/取得するメソッドは、GetまたはSetというプレフィックスを使用する必要があります。
- 配列のサイズを返すメソッドの名前は「Size」にする必要があります。
- クラス属性をリセットするメソッドの名前は「Clear」にする必要があります。
- 文字列に変換するメソッドの名前は「ToString」にする必要があります。
- コンテキストクラス内の名前の冗長性を避けます。たとえば、CQueryParamクラスにはAddParam()メソッドがありますが、これは意味がありません。すでにパラメータのコンテキストにあるため、理想的なのはAdd()だけです。
とはいえ、メソッドの実装は変更せず、名前のみを変更するため、名前を変更するライブラリ内のすべてのメソッドをリストしたり、ソースコードを提供したりすることはしません。ただし、変更を反映させた後、ライブラリ内のすべてのクラスと更新されたメソッド名、およびその関係を示す図を以下に示します。
結論
この最後の記事で、HTTP通信を簡素化するために設計されたConnexusライブラリの作成に関する連載を締めくくります。長い旅でした。基本を学び、より高度な設計手法やコードの洗練技術に深く掘り下げ、さらにObserverパターンを探求して、動的で柔軟なシステムに不可欠な反応性をConnexusに加えました。私たちはこのパターンを実際に実装し、アプリケーションのさまざまな部分が自動的に変更に反応できるようにして、堅牢で適応性のある構造を作り上げました。
Observerパターンに加え、ファイルおよびフォルダーのアーキテクチャ全体を整理し、コードをモジュール化して直感的に扱えるようにしました。さらに、メソッドの名前を変更して、より明確で一貫性のある使い方を実現しました。これらは、クリーンコードと長期的なメンテナンスの観点で大きな違いを生み出す細かな部分です。
Connexusは、HTTP統合をできるだけシンプルで直感的におこなえるように設計されており、このシリーズを通して、プロセスの重要なポイントを紹介し、それを可能にした設計上の選択を明らかにできたことを願っています。この最終回の記事が、ConnexusがHTTP統合を簡素化するだけでなく、継続的な改善のインスピレーションにもなることを願っています。私と共にこの旅に出てくださり、ありがとうございました。Connexusがあなたのプロジェクトにとって頼もしい味方となることを願っています。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16377





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索