English 日本語
preview
Der Client im Connexus (Teil 7): Hinzufügen der Client-Schicht

Der Client im Connexus (Teil 7): Hinzufügen der Client-Schicht

MetaTrader 5Beispiele | 20 März 2025, 09:36
109 0
joaopedrodev
joaopedrodev

Einführung

Dieser Artikel ist die Fortsetzung einer Reihe von Artikeln, in denen wir eine Bibliothek namens Connexus aufbauen werden. Im ersten Artikel haben wir die grundlegende Funktionsweise der Funktion WebRequest verstanden, jeden ihrer Parameter kennengelernt und auch einen Beispielcode erstellt, der die Verwendung dieser Funktion und ihre Schwierigkeiten demonstriert. Im letzten Artikel haben wir verstanden, was http-Methoden sind und auch, was die Statuscodes sind, die vom Server zurückgegeben werden und die Auskunft darüber geben, ob eine Anfrage erfolgreich bearbeitet wurde oder ob ein Fehler vom Client oder Server erzeugt wurde.

In diesem siebten Artikel der Serie werden wir den am meisten erwarteten Teil der gesamten Bibliothek hinzufügen, wir werden die Anfrage mit der WebRequest-Funktion machen, wir werden nicht direkt den Zugriff darauf erstellen, es werden einige Klassen und Schnittstellen im Prozess sein. Los geht's!

Um Sie an den aktuellen Stand der Bibliothek zu erinnern, sehen Sie hier das aktuelle Diagramm:

Ziel ist es, ein CHttpRequest-Objekt zu erhalten, d. h. eine fertige HTTP-Anforderung, die bereits mit Header, Body, URL, Methode und Timeout konfiguriert ist, und eine HTTP-Anforderung mit der Funktion WebRequest zu senden. Außerdem muss er die Anfrage verarbeiten und ein CHttpResponse-Objekt mit den Antwortdaten wie Header, Body, Statuscode und Gesamtdauer der Anfrage zurückgeben.


Erstellen der Klasse CHttpClient

Erstellen wir die am meisten erwartete Klasse von allen, die letzte Klasse der Bibliothek, die der Nutzer der Bibliothek instanziieren und verwenden wird. Diese Klasse heißt CHttpClient.mqh und befindet sich im folgenden Pfad: Include/Connexus/Core/CHttpClient.mqh. Ursprünglich wird die Datei ähnlich wie diese aussehen, ich habe bereits die entsprechenden Importe hinzugefügt:
//+------------------------------------------------------------------+
//|                                                   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"
//+------------------------------------------------------------------+
//| class : CHttpClient                                              |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CHttpClient                                        |
//| Heritage    : No heritage                                        |
//| Description : Class responsible for linking the request and      |
//|               response object with the transport layer.          |
//|                                                                  |
//+------------------------------------------------------------------+
class CHttpClient
  {
public:
                     CHttpClient(void);
                    ~CHttpClient(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHttpClient::CHttpClient(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CHttpClient::~CHttpClient(void)
  {
  }
//+------------------------------------------------------------------+
Diese Klasse hat die Aufgabe, ein Request-Objekt, d. h. CHttpRequest, mit der Funktion WebRequest in eine HTTP-Anfrage umzuwandeln und ein CHttpResponse-Objekt mit den Daten zurückzugeben. Lassen Sie uns diese Klasse erstellen. Dazu erstellen wir eine Methode Send(), die zwei Objekte empfangen muss, CHttpRequest und CHttpResponse, und die ein booleschen Wert zurückgeben muss.
class CHttpClient
  {
public:
                     CHttpClient(void);
                    ~CHttpClient(void);
   
   //--- Basis function
   bool              Send(CHttpRequest &request, CHttpResponse &response);
  };

Lassen Sie uns an der Implementierung dieser Funktion arbeiten. Schauen wir uns zunächst kurz die Parameter der Funktion WebRequest an. Es gibt 2 Varianten, von denen wir zunächst diejenige mit weniger Parametern verwenden werden:

int  WebRequest(
   const string      method,           // HTTP method
   const string      url,              // URL
   const string      headers,          // headers 
   int               timeout,          // timeout
   const char        &data[],          // the array of the HTTP message body
   char              &result[],        // an array containing server response data
   string            &result_headers   // headers of server response
);
Alle diese Parameter sind bereits in den empfangenen Objekten vorhanden und konfiguriert, sodass sie nur noch hinzugefügt werden müssen. Die Umsetzung würde folgendermaßen aussehen:
//+------------------------------------------------------------------+
//| Basis function                                                   |
//+------------------------------------------------------------------+
bool CHttpClient::Send(CHttpRequest &request, CHttpResponse &response)
  {
   //--- 1. Request
   uchar body_request[];
   request.Body().GetAsBinary(body_request);
   
   //--- 2. Response
   uchar body_response[];
   string headers_response;
   
   //--- 3. Send
   ulong start = GetMicrosecondCount();
   int status_code = WebRequest(
      request.Method().GetMethodDescription(),     // HTTP method
      request.Url().FullUrl(),                     // URL
      request.Header().Serialize(),                // Headers
      request.Timeout(),                           // Timeout
      body_request,                                // The array of the HTTP message body
      body_response,                               // An array containing server response data
      headers_response                             // Headers of server response
   );
   ulong end = GetMicrosecondCount();
   
   //--- 4. 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);
   
   //--- 5. Return is success
   return(response.StatusCode().IsSuccess());
  }
//+------------------------------------------------------------------+

Ich habe in den Kommentaren einige Zahlen hinzugefügt, um die einzelnen Schritte zu erläutern, und es ist sehr wichtig, dass Sie verstehen, was hier geschieht, da dies die zentrale Funktion der Bibliothek ist:

  1. REQUEST: Hier erstellen wir ein Array vom Typ uchar mit dem Namen body_request. Wir greifen auf den Hauptteil, dem Body, der Anfrage zu und erhalten den Hauptteil der Anfrage in binärer Form, indem wir das Array body_request übergeben, sodass dieses Array durch Einfügen der Body-Daten verändert wird.
  2. RESPONSE: Wir erstellen zwei Variablen, die von der Funktion WebRequest verwendet werden, um den Hauptteil und die Kopfzeile (header) der Antwort vom Server zurückzugeben.
  3. SENDEN: Dies ist das Herzstück des Ganzen. Wir rufen die Funktion WebRequest auf und übergeben alle Parameter, einschließlich URL und Timeout, sowie die in Schritt 2 erstellten Variablen, um den Hauptteil und die Kopfzeile der Antwort zu erhalten. Hier erstellen wir auch zwei weitere Hilfsvariablen, die die Funktion GetMicrosecondCount() verwenden, um die Zeit vor und nach der Anfrage zu speichern. Auf diese Weise erhalten wir die Zeit vor und nach der Anfrage, um die Dauer der Anfrage in Millisekunden zu berechnen.
  4. DATEN ALS ANTWORT HINZUFÜGEN: In diesem Fall verwenden wir nach der Beantwortung der Anfrage, unabhängig davon, ob sie erfolgreich war oder nicht, die Funktion Clear(), um alle Werte des Antwortobjekts zurückzusetzen, und wir ergänzen die Dauer anhand der folgenden Formel: (Ende - Anfang)/1000. Wir definieren auch den von der Funktion zurückgegebenen Statuscode und fügen den Hauptteil und die Kopfzeile zum Antwortobjekt hinzu.
  5. RÜCKGABE: Im letzten Schritt wird geprüft, ob die Anfrage einen Erfolgscode (zwischen 200 und 299) zurückgegeben hat. Auf diese Weise ist es möglich, festzustellen, ob die Anfrage abgeschlossen wurde, und für weitere Details einfach den Inhalt des Antwortobjekts, d. h. CHttpResponse, zu überprüfen.

Mit der Erstellung dieser Klasse würde das aktualisierte Diagramm etwa so aussehen:

Am Ende befinden sich alle Klassen in HttpClient. Lassen Sie uns einen einfachen Test durchführen.



Erster Test

Führen wir den ersten und einfachsten Test mit allem durch, was wir bisher gebaut haben. Lassen Sie uns eine GET-Anfrage und eine POST-Anfrage an httpbin.org stellen, einen kostenlosen Online-Dienst, mit dem Sie HTTP-Anfragen testen können. Es wurde von kennethreitz erstellt und ist ein OpenSource-Projekt (link). Wir haben diese öffentliche API bereits in früheren Artikeln verwendet, aber um es kurz zu erklären: httpbin funktioniert wie ein Spiegel, d. h. alles, was wir an den Server senden, wird an den Client zurückgesendet. Dies ist sehr nützlich für das Testen von Anwendungen, bei denen wir einfach wissen wollen, ob die Anfrage abgeschlossen wurde und welche Daten der Server erhalten hat.

Im Code erstellen wir eine neue Datei im Ordner Experts mit dem Namen TestRequest.mq5. Der endgültige Pfad lautet Experts/Connexus/Test/TestRequest.mq5. Wir werden nur die OnInit-Funktion aus der Datei verwenden, also verwerfen wir den Rest. Der Code wird wie folgt aussehen:

//+------------------------------------------------------------------+
//|                                                  TestRequest.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"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Initialize
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Importieren wir die Bibliothek, indem wir den Import mit #include

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Connexus/Core/HttpClient.mqh>

Lassen Sie uns nun drei Objekte erstellen:

  • CHttpRequest : Hier werden die URL und die HTTP-Methode definiert. Timeout, Hauptteil und Kopfzeile sind bei GET-Anfragen optional, der Timeout hat den Standardwert von 5000 Millisekunden (5 Sekunden).
  • CHttpResponse : Speichert das Ergebnis der Anfrage mit dem Statuscode und der Beschreibung sowie die Dauer der Anfrage in Millisekunden.
  • CHttpClient : Die Klasse, die die Anfrage tatsächlich ausführt.
//--- Request object
CHttpRequest request;
request.Method() = HTTP_METHOD_GET;
request.Url().Parse("https://httpbin.org/get");

//--- Response object
CHttpResponse response;

//--- Client http object
CHttpClient client;

Und schließlich rufen wir die Funktion Send() auf, um die Anfrage zu stellen und die Daten auf dem Terminal auszudrucken. Der vollständige Code sieht wie folgt aus:

//+------------------------------------------------------------------+
//|                                                  TestRequest.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 files                                                    |
//+------------------------------------------------------------------+
#include <Connexus/Core/HttpClient.mqh>
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Request object
   CHttpRequest request;
   request.Method() = HTTP_METHOD_GET;
   request.Url().Parse("https://httpbin.org/get");
   
   //--- Response object
   CHttpResponse response;
   
   //--- Client http object
   CHttpClient client;
   client.Send(request,response);
   Print(response.FormatString());
   
   //--- Initialize
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
Bei der Ausführung des Codes erhalten wir dieses Ergebnis im Terminal:
HTTP Response:
---------------
Status Code: HTTP_STATUS_OK [200] - OK
Duration: 845 ms

---------------
Headers:
Date: Fri, 18 Oct 2024 17:52:35 GMT
Content-Type: application/json
Content-Length: 380
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
---------------
Body:
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Accept-Language": "pt,en;q=0.5", 
    "Host": "httpbin.org", 
    "User-Agent": "MetaTrader 5 Terminal/5.4620 (Windows NT 11.0.22631; x64)", 
    "X-Amzn-Trace-Id": "Root=1-6712a063-5c19d0e03b85df9903cb0e91"
  }, 
  "origin": "XXX.XX.XX.XX", 
  "url": "https://httpbin.org/get"
}

---------------

Dies sind die Daten des Antwortobjekts, d. h. der Statuscode mit Beschreibung, die Dauer der Anfrage in Millisekunden, die Kopfzeile und der Antwortkörper. Das macht das Leben der Entwickler viel einfacher, wenn sie ihre MQL5-Programme mit externen APIs verbinden. Wir haben es geschafft, eine HTTP-Anfrage sehr einfach zu gestalten. Um einen Vergleich der Entwicklung zu haben, werde ich Sie daran erinnern, wie eine Anfrage ohne die Bibliothek und eine andere mit ihr funktioniert:

  • Ohne die Connexus-Bibliothek:
    int OnInit()
      {
    //--- Defining variables
       string method = "GET";                    // HTTP verb in string (GET, POST, etc...)
       string url = "https://httpbin.org/get";   // Destination URL
       string headers = "";                      // Request header
       int timeout = 5000;                       // Maximum waiting time 5 seconds
       char data[];                              // Data we will send (body) array of type char
       char result[];                            // Data received as an array of type char
       string result_headers;                    // String with response headers
       
       string body = "{\"key\":\"value\"}";
       StringToCharArray(body,data,0,WHOLE_ARRAY,CP_UTF8);
       ArrayRemove(data,ArraySize(data)-1);
       
    //--- Calling the function and getting the status_code
       int status_code = WebRequest(method,url,headers,timeout,data,result,result_headers);
       
    //--- Print the data
       Print("Status code: ",status_code);
       Print("Response: ",CharArrayToString(result)); // We use CharArrayToString to display the response in string form.
    
    //---
       return(INIT_SUCCEEDED);
      }
  • Mit der Connexus-Bibliothek:
    int OnInit()
      {
       //--- Request object
       CHttpRequest request;
       request.Method() = HTTP_METHOD_GET;
       request.Url().Parse("https://httpbin.org/get");
       request.Body().AddString("{\"key\":\"value\"}");
       
       //--- Response object
       CHttpResponse response;
       
       //--- Client http object
       CHttpClient client;
       client.Send(request,response);
       Print(response.FormatString());
       
       //--- Initialize
       return(INIT_SUCCEEDED);
      }

Es ist viel einfacher, mit der Bibliothek zu arbeiten, auf den Hauptteil in verschiedenen Formaten zuzugreifen, wie z.B. string, json oder binär und alle anderen verfügbaren Ressourcen. Es ist auch möglich, Teile der Anfrage wie URL, Hauptteil und Kopf zu ändern und das gleiche Objekt für eine andere Anfrage zu verwenden. Auf diese Weise können Sie auf vereinfachte Weise mehrere Anfragen hintereinander stellen.


Wir haben ein kleines Problem: COUPLING!

Es scheint so, als ob alles richtig funktioniert, und das tut es auch, aber alles kann verbessert werden, und hier in der Connexus-Bibliothek ist es nicht anders. Aber was ist denn nun das Problem? Wenn wir die Funktion WebRequest direkt in der CHttpClient-Klasse verwenden, haben wir einen stark gekoppelten Code, aber was ist ein stark gekoppelter Code?

Stellen Sie sich Folgendes vor: Sie bauen ein Kartenhaus. Jede Karte muss perfekt ausbalanciert sein, damit alles aufrecht bleibt. Stellen Sie sich vor, dass Sie statt einer leichten und flexiblen Burg, in der Sie eine Karte verschieben können, ohne alles umzustoßen, etwas haben, das wie ein Betonklotz aussieht. Wenn Sie eine Karte nehmen oder bewegen - passiert was? Das ganze Burg stürzt ein. Genau das passiert bei gekoppeltem Code.

Wenn wir in der Programmierung von Kopplung sprechen, ist das so, als ob die Teile Ihres Codes so miteinander verbunden wären, sodass, wenn ein Teil bewegt wird, auch mehrere andere bewegt werden müssen. Es ist wie mit dem festen Knoten, den man aus Versehen in den Schnürsenkel gemacht hat und der sich nur schwer wieder lösen lässt, ohne alles zu vermasseln. Das Ändern einer Funktion, das Modifizieren einer Klasse oder das Hinzufügen von etwas Neuem bereitet Kopfzerbrechen, weil alles miteinander verklebt ist.

Wenn zum Beispiel eine Funktion stark von einer anderen Funktion abhängt und diese Funktionen nicht getrennt voneinander „leben“ können, dann ist der Code stark gekoppelt. Und das ist schlecht, denn mit der Zeit wird es schwierig, sie zu pflegen, neue Funktionen hinzuzufügen oder sogar Fehler zu beheben, ohne Gefahr zu laufen, etwas kaputt zu machen. Dies ist genau das, was innerhalb der CHttpClient-Klasse passiert, wo Send() direkt von WebRequest abhängt; es ist derzeit nicht möglich, das eine vom anderen zu trennen. In einer idealen Welt wollen wir „entkoppelten“ Code, bei dem jedes Teil für sich arbeiten kann, wie Legosteine: Man kann sie ohne größere Probleme ein- und ausbauen.

Um weniger gekoppelten Code zu erstellen, verwenden wir Schnittstellen und Dependency Injection (Abhängigkeitsinjektion). Die Idee ist, eine Schnittstelle zu schaffen, die die notwendigen Operationen definiert, sodass die Klasse von dieser Abstraktion anstelle einer spezifischen Implementierung abhängt. So kann CHttpClient mit jedem Objekt kommunizieren, das die Schnittstelle implementiert, sei es WebRequest oder eine Mock-Funktion (FakeWebRequest). Schauen wir uns das Konzept der Schnittstellen und der Dependency Injection an:

  • Die Rolle der Schnittstellen: Schnittstellen ermöglichen es uns, einen Vertrag zu definieren, den andere Klassen implementieren können. In unserem Fall können wir eine Schnittstelle namens IHttpTransport erstellen, die die Methoden definiert, die CHttpClient benötigt, um eine HTTP-Anfrage durchzuführen. Auf diese Weise hängt CHttpClient von der IHttpTransport-Schnittstelle und nicht von der WebRequest-Funktion ab, wodurch die Kopplung verringert wird.
  • Dependency Injection: Um CHttpClient mit der konkreten Implementierung von IHttpTransport zu verbinden, verwenden wir Dependency Injection. Diese Technik besteht darin, die Abhängigkeit (WebRequest oder FakeWebRequest) als Parameter an CHttpClient zu übergeben, anstatt sie direkt innerhalb der Klasse zu instanziieren.

Was sind die Vorteile, wenn man den Code nicht gekoppelt lässt?

  • Wartbarkeit: Wenn man mit entkoppeltem Code etwas ändern muss, ist das so, als würde man ein Teil eines Autos austauschen, ohne den gesamten Motor zu zerlegen. Die Teile Ihres Codes sind unabhängig voneinander, sodass die Änderung eines Teils keine Auswirkungen auf die anderen hat. Möchten Sie einen Fehler beheben oder eine Funktion verbessern? Nehmen Sie die Änderung vor, ohne Angst haben zu müssen, dass der Rest des Systems zusammenbricht.
  • Wiederverwendbarkeit: Stellen Sie sich vor, Sie hätten Legosteine. Mit entkoppeltem Code können Sie Funktionsblöcke nehmen und sie in anderen Projekten oder Teilen des Systems verwenden, ohne an komplizierte Abhängigkeiten gebunden zu sein.
  • Prüfbarkeit: Durch die Verwendung einer Schnittstelle wird es möglich, WebRequest durch eine Mock-Funktion zu ersetzen, die das erwartete Verhalten imitiert, sodass Sie Tests durchführen können, ohne von echten HTTP-Anfragen abhängig zu sein. Dieses Konzept der Simulation bringt uns zum nächsten Thema: Mocks.


Was sind Mocks?

Mocks sind simulierte Versionen von realen Objekten oder Funktionen, die dazu dienen, das Verhalten einer Klasse zu testen, ohne auf externe Implementierungen angewiesen zu sein. Wenn wir CHttpClient von WebRequest mithilfe einer Schnittstelle trennen, haben wir die Möglichkeit, eine simulierte Version zu übergeben, die denselben Vertrag erfüllt, ein so genanntes „Mock“. Auf diese Weise können wir CHttpClient in einer kontrollierten Umgebung testen und vorhersagen, wie es in verschiedenen Antwortszenarien reagieren würde, ohne tatsächlich HTTP-Aufrufe zu tätigen.

In einem System, in dem CHttpClient direkte Aufrufe an WebRequest macht, sind die Tests begrenzt und hängen von der Konnektivität und dem Verhalten des Servers ab. Durch Einfügen einer Attrappe der WebRequest-Funktion, die simulierte Ergebnisse zurückgibt, können wir jedoch verschiedene Szenarien testen und die Antwort des CHttpClient isoliert validieren. Diese Mocks sind sehr nützlich, um sicherzustellen, dass sich die Bibliothek wie erwartet verhält, auch in Fehlersituationen oder bei unerwarteten Antworten.

Als Nächstes werden wir sehen, wie man Schnittstellen und Mocks implementiert, um die Flexibilität und Testbarkeit unserer CHttpRequest-Klasse zu verbessern.

Hands on Code

Wie sollen wir das in der Praxis im Code umsetzen? Wir werden einige fortgeschrittenere Sprachfunktionen wie Klassen und Schnittstellen verwenden. Wenn Sie bis hierher gekommen sind und die vorherigen Artikel gelesen haben, sind Sie bereits einigermaßen mit Klassen vertraut, aber lassen Sie uns das Konzept noch einmal durchgehen.

  • Klasse: Als „Vorlage“ für die Erstellung eines Objekts können Sie innerhalb einer Klasse Methoden und Attribute definieren. Methoden sind die Aktionen oder Verhaltensweisen, während Attribute die Merkmale oder Daten des Objekts sind. Nehmen wir ein Beispiel aus dem Kontext von MQL5. Sie haben eine Klasse namens CPosition. In dieser Klasse können Sie Attribute wie Preis, Typ (Kauf oder Verkauf), Take-Profit, Stop-Loss, Volumen usw. definieren. Die Klasse wird zur Erstellung von Objekten (Instanzen) verwendet, und dieses Objekt hat seine eigenen einzigartigen Eigenschaften.
  • Schnittstelle: Eine Schnittstelle ist wie ein „Vertrag“, der nur die Methoden definiert, die eine Klasse implementieren muss, aber nicht sagt, wie diese Methoden implementiert werden sollen. Eine Schnittstelle enthält nicht die Implementierung der Methoden, sondern nur deren Signaturen (Namen, Parameter und Rückgabetypen).

Schauen wir uns nun ein praktisches Beispiel im Zusammenhang mit der Bibliothek an. Lassen Sie uns Schritt für Schritt vorgehen. Wir wollen auf die Funktion WebRequest zugreifen, aber ich möchte nicht direkt auf sie zugreifen, weil wir gesehen haben, dass dies den Code zu sehr koppelt. Ich möchte es Ihnen ermöglichen, Ihre eigene WebRequest-Funktion zu haben und die Bibliothek kann sie ohne Komplikationen verwenden. Schaffen wir Schichten zwischen der Funktion und denjenigen, die auf diese Funktion zugreifen. Diese Ebene wird die Schnittstelle sein.

Ich werde das anhand einiger Diagramme erklären, bevor ich direkt zum Code übergehe.

Wie im Diagramm dargestellt, greift die Klasse HttpClient direkt auf die Funktion WebRequest zu. Fügen wir eine Schicht zwischen der Klasse und der Funktion hinzu, so wird diese Schicht die Schnittstelle IHttpTransport sein.

Mit dieser Schnittstelle können wir die Klasse CHttpTransport erstellen, um diese Schnittstelle mit der Funktion WebRequest zu implementieren. Wie in der nachstehenden Abbildung dargestellt:

Wir können mehrere verschiedene Implementierungen für die Schnittstelle erstellen und sie an die CHttpClient-Klasse übergeben. Die Bibliothek verwendet CHttpTransport, dessen Implementierung WebRequest ist, aber wir können so viele andere hinzufügen, wie wir wollen, wie im Diagramm gezeigt:

In der Praxis wird die Ordnerstruktur wie folgt aussehen: Wir erstellen einen neuen Ordner mit dem Namen „Interface“ und zwei weitere Dateien, wie unten gezeigt:
|--- Connexus
|--- |--- Core
|--- |--- |--- HttpTransport.mqh
|--- |--- Interface
|--- |--- |--- IHttpTransport.mqh

Zunächst erstellen wir den Schnittstellencode, der die Eingabe und Ausgabe der Funktion definiert:

//+------------------------------------------------------------------+
//| transport interface                                              |
//+------------------------------------------------------------------+
interface IHttpTransport
  {
   int               Request(const string method,const string url,const string cookie,const string referer,int timeout,const char &data[],int data_size,char &result[],string &result_headers);
   int               Request(const string method,const string url,const string headers,int timeout,const char &data[],char &result[],string &result_headers);
  };
//+------------------------------------------------------------------+
Beachten Sie, dass ich nur die Funktion deklariere, aber nicht den Körper der Funktion definiere. Ich teile dem Compiler mit, dass die Funktion eine ganze Zahl zurückgeben soll, der Name der Methode wird Request sein, und ich sage ihm, welche Parameter erwartet werden. Um den Hauptteil zu definieren, muss die Klasse verwendet werden, und wir teilen dem Compiler mit, dass die Klasse die Schnittstelle implementieren muss, in der Praxis sieht das so aus:
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "../Interface/IHttpTransport.mqh"
//+------------------------------------------------------------------+
//| class : CHttpTransport                                           |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CHttpTransport                                     |
//| Heritage    : IHttpTransport                                     |
//| Description : class that implements the transport interface,     |
//|               works as an extra layer between the request and    |
//|               the final function and WebRequest.                 |
//|                                                                  |
//+------------------------------------------------------------------+
class CHttpTransport : public IHttpTransport
  {
public:
                     CHttpTransport(void);
                    ~CHttpTransport(void);

   int               Request(const string method,const string url,const string cookie,const string referer,int timeout,const char &data[],int data_size,char &result[],string &result_headers);
   int               Request(const string method,const string url,const string headers,int timeout,const char &data[],char &result[],string &result_headers);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHttpTransport::CHttpTransport(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CHttpTransport::~CHttpTransport(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CHttpTransport::Request(const string method,const string url,const string cookie,const string referer,int timeout,const char &data[],int data_size,char &result[],string &result_headers)
  {
   return(WebRequest(method,url,cookie,referer,timeout,data,data_size,result,result_headers));
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CHttpTransport::Request(const string method,const string url,const string headers,int timeout,const char &data[],char &result[],string &result_headers)
  {
   return(WebRequest(method,url,headers,timeout,data,result,result_headers));
  }
//+------------------------------------------------------------------+

Durch die Verwendung von Schnittstellen und Mocks haben wir die Klasse CHttpRequest von der Funktion WebRequest entkoppelt und damit eine flexiblere und besser wartbare Bibliothek geschaffen. Dieser Ansatz gibt Entwicklern, die Connexus verwenden, die Flexibilität, das Verhalten der Bibliothek in verschiedenen Szenarien zu testen, ohne echte Anfragen zu stellen, was eine reibungslosere Integration und eine schnelle Anpassung an neue Anforderungen und Funktionen ermöglicht.

Jetzt müssen wir dies nur noch in der Klasse CHttpClient verwenden, also importieren wir die Schnittstelle IHttpTransport und erstellen eine Instanz:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "../Interface/IHttpTransport.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:
   
   IHttpTransport    *m_transport;                       // Instance to store the transport implementation
   
public:
                     CHttpClient(void);
                    ~CHttpClient(void);
   
   //--- Basis function
   bool              Send(CHttpRequest &request, CHttpResponse &response);
  };
//+------------------------------------------------------------------+

Um die Injektion von Abhängigkeiten durchzuführen, fügen wir der Klasse einen neuen Konstruktor hinzu, der die zu verwendende Transportschicht empfängt und in m_transport speichert. Wir werden auch den Standardkonstruktor so ändern, dass er, wenn keine Transportschicht definiert ist, diese automatisch mit CHttpTransport implementiert:

//+------------------------------------------------------------------+
//| 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:
   
   IHttpTransport    *m_transport;                       // Instance to store the transport implementation
   
public:
                     CHttpClient(IHttpTransport *transport);
                     CHttpClient(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHttpClient::CHttpClient(IHttpTransport *transport)
  {
   m_transport = transport;
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHttpClient::CHttpClient(void)
  {
   m_transport = new CHttpTransport();
  }
//+------------------------------------------------------------------+

Da wir nun die Transportschicht haben, können wir sie in der Funktion „Send“ verwenden

//+------------------------------------------------------------------+
//| 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;
   
   //--- 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();
   
   //--- 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());
  }
//+------------------------------------------------------------------+

Es sei daran erinnert, dass sich die Art und Weise, wie die Klasse verwendet wird, nicht ändert, d. h. die gleichen Codes, die wir in diesem Artikel im Abschnitt „Tests“ verwendet haben, sind weiterhin gültig. Der Unterschied ist, dass wir jetzt die endgültige Funktion der Bibliothek ändern können.


Schlussfolgerung

Damit ist eine weitere Phase der Bibliothek abgeschlossen, nämlich die Erstellung der Client-Schicht. Durch die Verwendung von Schnittstellen und Mocks haben wir die CHttpClient-Klasse von der WebRequest-Funktion entkoppelt und damit eine flexiblere und einfach zu wartende Bibliothek geschaffen. Dieser Ansatz gibt Entwicklern, die Connexus verwenden, die Flexibilität, das Verhalten der Bibliothek in verschiedenen Szenarien zu testen, ohne echte Anfragen zu stellen, was eine reibungslosere Integration und eine schnelle Anpassung an neue Anforderungen und Funktionen ermöglicht.

Die Praxis der Entkopplung mit Hilfe von Schnittstellen und Mocks verbessert die Codequalität erheblich und trägt zur Erweiterbarkeit, Testbarkeit und langfristigen Wartbarkeit bei. Dieser Ansatz ist besonders vorteilhaft für Bibliotheken, bei denen die Entwickler oft flexible Tests und modulare Ersetzungen benötigen, was einen Mehrwert für alle Nutzer der Connexus-Bibliothek darstellt.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/16324

Feature Engineering mit Python und MQL5 (Teil II): Winkel des Preises Feature Engineering mit Python und MQL5 (Teil II): Winkel des Preises
Im MQL5-Forum gibt es viele Beiträge, in denen um Hilfe bei der Berechnung der Steigung von Preisänderungen gebeten wird. In diesem Artikel wird eine Möglichkeit zur Berechnung des Winkels aufgezeigt, der sich aus den Kursveränderungen eines beliebigen Marktes ergibt, mit dem Sie handeln möchten. Außerdem werden wir die Frage beantworten, ob die Entwicklung dieser neuen Funktion den zusätzlichen Aufwand und die investierte Zeit wert ist. Wir werden untersuchen, ob die Steigung des Kurses die Genauigkeit unseres KI-Modells bei der Vorhersage des USDZAR-Paares auf dem M1 verbessern kann.
Von Python zu MQL5: Eine Reise in quanteninspirierte Handelssysteme Von Python zu MQL5: Eine Reise in quanteninspirierte Handelssysteme
Der Artikel befasst sich mit der Entwicklung eines quanteninspirierten Handelssystems, das von einem Python-Prototyp zu einer MQL5-Implementierung für den realen Handel übergeht. Das System nutzt die Prinzipien der Quanteninformatik wie Überlagerung und Verschränkung, um Marktzustände zu analysieren, obwohl es auf klassischen Computern mit Quantensimulatoren läuft. Zu den wichtigsten Merkmalen gehören ein Drei-Qubit-System zur gleichzeitigen Analyse von acht Marktzuständen, 24-Stunden-Rückblicke und sieben technische Indikatoren für die Marktanalyse. Auch wenn die Genauigkeitsraten bescheiden erscheinen mögen, bieten sie in Verbindung mit geeigneten Risikomanagementstrategien einen erheblichen Vorteil.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 46): Ichimoku MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 46): Ichimoku
Der Ichimuko Kinko Hyo ist ein bekannter japanischer Indikator, der als Trenderkennungssystem dient. Wir untersuchen dies, wie schon in früheren ähnlichen Artikeln, Muster für Muster und bewerten auch die Strategien und Testberichte mit Hilfe der MQL5-Assistentenbibliothek Klassen und Assembly.
Erstellen von einem Trading Administrator Panel in MQL5 (Teil VI): Schnittstelle für mehrere Funktionen (I) Erstellen von einem Trading Administrator Panel in MQL5 (Teil VI): Schnittstelle für mehrere Funktionen (I)
Die Rolle des Handelsadministrators geht über die reine Telegram-Kommunikation hinaus; er kann auch verschiedene Kontrolltätigkeiten ausüben, einschließlich Auftragsmanagement, Positionsverfolgung und Schnittstellenanpassung. In diesem Artikel geben wir praktische Einblicke in die Erweiterung unseres Programms zur Unterstützung mehrerer Funktionalitäten in MQL5. Dieses Update zielt darauf ab, die Beschränkung des aktuellen Admin Panels zu überwinden, das sich in erster Linie auf die Kommunikation konzentriert, und ermöglicht es, ein breiteres Spektrum von Aufgaben zu bewältigen.