English Русский 日本語
preview
Entwurfsmuster in der Softwareentwicklung und MQL5 (Teil 4): Verhaltensmuster 2

Entwurfsmuster in der Softwareentwicklung und MQL5 (Teil 4): Verhaltensmuster 2

MetaTrader 5Handel | 1 März 2024, 10:27
232 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Einführung

In diesem Artikel werden wir die verhaltensbasierten Entwurfsmuster (Behavioral Patterns) vervollständigen, um das Thema der Entwurfsmuster in Software zu vervollständigen und wie wir sie in MQL5 verwenden können. Wir identifizierten in den vorangegangenen Artikeln alle Erzeugungsmuster in dem Artikel Entwurfsmuster in der Softwareentwicklung und MQL5 (Teil I): Erzeugungsmuster, alle strukturellen Muster im Artikel Entwurfsmuster in der Softwareentwicklung und MQL5 (Teil 2) : Strukturelle Muster. Im vorherigen Artikel über Entwurfsmuster in der Softwareentwicklung und MQL5 (Teil 3): Verhaltensmuster identifizierten wir einige Verhaltensmuster wie: Kette der Verantwortung (Chain of responsibility), Anweisung (Command), Interpreter, Iterator und Mediator. Wir haben auch festgestellt, was Entwurfmuster sind, d.h. Muster, die verwendet werden können, um die Methode der Kommunikation zwischen Objekten zu definieren und zu verwalten.

In diesem Artikel werden wir das, was von den Verhaltensmustern übrig geblieben ist, mit folgenden Themen vervollständigen:

  • Memento: Es kann verwendet werden, um ein Objekt in einem gespeicherten Zustand wiederherzustellen, indem der interne Zustand des Objekts erfasst und externalisiert wird, ohne die Kapselung zu verletzen.
  • Observer: Der Beobachter (Observer) kann verwendet werden, um eine eins-zu-viele-Abhängigkeit zwischen Objekten zu definieren, sodass, wenn ein Objekt seinen Zustand ändert, alle seine Abhängigkeiten eine Benachrichtigung erhalten und automatisch aktualisiert werden.
  • State: Der Status (state) kann dazu verwendet werden, dass ein Objekt sein Verhalten ändert, wenn sich sein interner Zustand ändert. Das Objekt scheint seine Klasse zu ändern.
  • Strategy: Sie kann verwendet werden, um eine Familie von Algorithmen zu identifizieren, sie zu kapseln und austauschbar zu machen. Diese Strategie ermöglicht es, den Algorithmus unabhängig von den Clients, die ihn verwenden, zu verändern.
  • Template Method: Eine Methodenschablone (Template Method) kann verwendet werden, um die Grundlagen eines Algorithmus in einem Vorgang zu identifizieren, wobei einige Schritte Unterklassen überlassen werden, die sie neu identifizieren können, ohne die Struktur der Algorithmen zu ändern.
  • Visitor: Der Besucher (Visitor) kann dazu verwendet werden, eine neue Operation zu identifizieren, ohne dass dies Auswirkungen auf die Klassen der Elemente hat, auf denen die Operation ausgeführt wird.
  • Schlussfolgerung

Wir haben bereits festgestellt, dass Verhaltensmuster die Muster für ein Verhalten sind, die sich mit der Zuweisung und Festlegung von Verantwortlichkeiten zwischen Objekten befassen. Sie erkennen auch, wie Objekte miteinander kommunizieren oder interagieren können. Sie charakterisieren einen komplexen Kontrollfluss, der zur Laufzeit nur schwer nachvollziehbar ist. Sie ermöglichen es Ihnen, sich nur auf die Art und Weise zu konzentrieren, wie Objekte miteinander verbunden sind, und verlagern Ihren Fokus weg vom Kontrollfluss.

Wenn Sie meine früheren Artikel in dieser Reihe gelesen haben, werden Sie mit unserem Ansatz vertraut sein, jedes Muster anhand der folgenden Punkte zu präsentieren:

  • Was bewirkt das Muster?
  • Was löst das Muster?
  • Wie können wir sie in MQL5 verwenden?

Es ist wichtig zu erwähnen, dass, wenn Sie das Thema OOP (objektorientierte Programmierung) verstehen, Ihnen dies dabei helfen wird, das Thema Entwurfsmuster besser zu verstehen. Wenn Sie etwas von diesem Thema lesen oder lernen möchten, können Sie auch meinen vorherigen Artikel Verstehen der MQL5 Objektorientierte Programmierung (OOP) lesen. Ich hoffe, dass Sie meine Artikel nützlich finden, um Ihre Programmierkenntnisse zu verbessern, indem Sie eines der wichtigsten Themen identifizieren, nämlich Entwurfsmuster, um sauberen, erweiterbaren, wiederverwendbaren und gut getesteten Code zu schreiben.

Haftungsausschluss: Alle Informationen werden in der vorliegenden Form nur zu Informationszwecken bereitgestellt und sind nicht für Handelszwecke oder als Ratschläge gedacht. Die Informationen garantieren keinen Erfolg. Wenn Sie sich dafür entscheiden, diese Materialien auf einem Ihrer Handelskonten zu verwenden, tun Sie dies auf eigenes Risiko und Sie sind allein verantwortlich.

Memento

In diesem Abschnitt werden wir das Memento-Muster als verhaltensorientiertes Entwurfsmuster identifizieren. Das Memento-Muster kann verwendet werden, um den Zustand eines Objekts zu externalisieren, um eine Rollback-Funktionalität bereitzustellen, und es ist auch als Token bekannt.

Was bewirkt das Muster?

Wir können das Memento-Muster verwenden, wenn wir eine Momentaufnahme des Zustands des Objekts speichern müssen, die zu einem späteren Zeitpunkt wiederhergestellt werden kann, und wenn eine direkte Schnittstelle zum Abrufen des Zustands die Details der Ausführung offenlegen und die Kapselung des Objekts aufheben würde. Dieses Muster erfasst und externalisiert also den Objektzustand, um ihn später wiederherzustellen. Das folgende Diagramm zeigt die Struktur dieses Musters und wie man es einsetzen kann:

Memento

Wie aus dem vorstehenden Schaubild hervorgeht, haben wir folgende Teilnehmer:

  • Memento: Es speichert den Zustand nach Bedarf nach dem Ermessen des Originator-Objekts, das seinen Zustand speichert. Es gewährt keinen Zugriff auf Objekte außer dem Originator. Es kann so viel oder so wenig vom internen Zustand des Originator speichern, wie es nach dem Ermessen des Originators erforderlich ist. Es hat zwei Schnittstellen, eng oder weit, je nach dem Standpunkt des Caretakers oder des Originators.
  • Originator: Er erstellt das Memento, das den aktuellen internen Zustands-Snapshot enthält, und stellt den internen Zustand unter Verwendung des Mementos wieder her.
  • Caretaker: Er ist für die Speicherung des Mementos verantwortlich, ohne den Inhalt des Mementos zu prüfen.

Bei der Verwendung dieser Art von Mustern gibt es einige Fallstricke, die im Folgenden aufgeführt sind:

  • Es kann „teuer“ werden, wenn es sich um ein großes Exemplar handelt.
  • Da es nur eine begrenzte Anzahl von Speicherplätzen für Schnappschüsse gibt, geht ein Teil der Geschichte verloren.
  • Außer dem Memento werden keine weiteren Informationen preisgegeben.

Was löst das Entwurfsmuster?

Durch die Verwendung dieses Musters können die folgenden Probleme gelöst werden:

  • Dabei bleiben die Kapselungsgrenzen erhalten.
  • Sie identifiziert schmale und breite Schnittstellen.
  • Sie kann zur Vereinfachung des Originator verwendet werden.

Wie können wir sie in MQL5 verwenden?

In diesem Teil werden wir versuchen, das Memento-Muster in MQL5 zu verwenden, und das kann durch die folgenden Schritte geschehen:

Deklarieren der Klasse Memento mit dem Schlüsselwort class:

class Memento
  {
protected:
   string            m_state;
public:
   string            GetState(void);
   void              SetState(string);
                     Memento(string);
  };
Memento::Memento(string state):
   m_state(state)
  {
  }
string Memento::GetState(void)
  {
   return m_state;
  }
void Memento::SetState(string state)
  {
   m_state=state;
  }

Deklarieren der Originator-Klasse:

class Originator
  {
protected:
   string            m_state;
public:
   void              SetMemento(Memento& memento);
   Memento*          CreateMemento(void);
   string            State(void);
   void              State(string);
  };
void Originator::SetMemento(Memento& memento)
  {
   m_state=memento.GetState();
  }
Memento* Originator::CreateMemento(void)
  {
   return new Memento(m_state);
  }
void Originator::State(string state)
  {
   m_state=state;
  }
string Originator::State(void)
  {
   return m_state;
  }

Deklarieren der Klasse Caretaker:

class Caretaker
  {
public:
   Memento*          memento;
                    ~Caretaker(void);
  };
Caretaker::~Caretaker(void)
  {
   if(CheckPointer(memento)==1)
     {
      delete memento;
     }
  }

Im Folgenden finden Sie den kompletten Code in einem Block für die Verwendung des Memento-Musters in MQL5:

//+------------------------------------------------------------------+
//|                                                      Memento.mqh |
//+------------------------------------------------------------------+
class Memento
  {
protected:
   string            m_state;
public:
   string            GetState(void);
   void              SetState(string);
                     Memento(string);
  };
Memento::Memento(string state):
   m_state(state)
  {
  }
string Memento::GetState(void)
  {
   return m_state;
  }
void Memento::SetState(string state)
  {
   m_state=state;
  }
class Originator
  {
protected:
   string            m_state;
public:
   void              SetMemento(Memento& memento);
   Memento*          CreateMemento(void);
   string            State(void);
   void              State(string);
  };
void Originator::SetMemento(Memento& memento)
  {
   m_state=memento.GetState();
  }
Memento* Originator::CreateMemento(void)
  {
   return new Memento(m_state);
  }
void Originator::State(string state)
  {
   m_state=state;
  }
string Originator::State(void)
  {
   return m_state;
  }
class Caretaker
  {
public:
   Memento*          memento;
                    ~Caretaker(void);
  };
Caretaker::~Caretaker(void)
  {
   if(CheckPointer(memento)==1)
     {
      delete memento;
     }
  }

Observer

In diesem Teil werden wir ein weiteres Verhaltensmuster identifizieren, nämlich das Muster des Observers (Beobachter). Es definiert eine eins-zu-viele-Abhängigkeit zwischen Objekten, sodass, wenn ein Objekt seinen Zustand ändert, alle seine Abhängigkeiten benachrichtigt und automatisch aktualisiert werden. Es ist auch als Dependents und Publish-Subscribe bekannt.

Was bewirkt das Muster?

Wir können das Observer-Muster verwenden, wenn wir Abstraktionsaspekte in separaten Objekten kapseln müssen, um sie unabhängig voneinander variieren und wiederverwenden zu können, wenn es notwendig ist, andere Objekte zu ändern, wenn wir die Anzahl der zu ändernden Objekte nicht kennen, und wenn wir das Objekt benötigen, um eine Benachrichtigung an andere Objekte zu senden, ohne dass eine Kopplung zwischen diesen Objekten stattfindet.

Auf der Grundlage der obigen Ausführungen kann dieses Muster in dem folgenden Diagramm dargestellt werden, um ein besseres Verständnis für die Funktionsweise dieses Musters zu erhalten:

Observer

Wie wir in der vorhergehenden Grafik sehen, können wir sagen, dass wir folgende Teilnehmer in diesem Muster haben:

  • Subject: Subject identifiziert seine Observer und die Anzahl der Observer, von denen Subject beobachtet werden kann. Zum Anheften und Lösen von Objekten des Observers stellt Subject die Schnittstelle zur Verfügung.
  • Observer: Er identifiziert die aktualisierte Schnittstelle für Objekte, die über Änderungen im Thema informiert werden müssen.
  • ConcreteSubject: Es speichert den Zustand in ConcreteObserver-Objekten und sendet bei einer Änderung des Zustands eine Benachrichtigung an die Observer.
  • ConcreteObserver: Er hat die Aufgabe, die folgenden drei Ziele zu erfüllen:
    • Beibehaltung des Verweises auf das Objekt des ConcreteSubject.
    • Sichern des Status‘.
    • Ausführen der Schnittstelle der Aktualisierung des Observers.

Bei der Verwendung des Observer-Musters gibt es einige Fallstricke, die im Folgenden erläutert werden:

  • Das Beobachtete zeigt nicht, welches Objekt seinen Zustand aktualisiert hat.
  • Große Aktualisierungen.
  • Es kann schwierig sein, Fehler zu beheben.

Was löst das Entwurfsmuster?

Nach dem, was wir über die Funktionsweise des Observer-Musters wissen, können wir sagen, dass es in der Lage ist, die folgenden Probleme zu lösen bzw. die folgenden Vorteile zu bieten, wenn es verwendet wird:

  • Es ermöglicht die abstrakte Kopplung zwischen dem Subject und dem Observer.
  • Es unterstützt die Interaktion oder Kommunikation im Rundfunk.

Wie können wir sie in MQL5 verwenden?

Jetzt ist es an der Zeit, eine Methode zur Verwendung des Observer-Musters in MQL5 zu sehen, und sie wird wie folgt aussehen:

Verwendung des Schlüsselworts Interface zur Deklaration des Observer-Bereichs, um die benötigten Klassen und Funktionen zu bestimmen:

interface Observer
  {
   void Update(string state);
  };

Verwendung des Schlüsselworts class zur Deklaration des Subjekts:

class Subject
  {
public:
                     Subject(void);
                    ~Subject(void);
   void              Attach(Observer* observer);
   void              Detach(Observer& observer);
   void              Notify(void);
   void              State(string state);
   string            State(void) {return m_state;}

protected:
   string            m_state;
   Observer*         m_observers[];

   int               Find(Observer& observer);
  };
Subject::Subject(void):
   m_state(NULL)
  {
  }
Subject::~Subject(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(CheckPointer(item)==1)
        {
         delete item;
        }
     }
  }
void Subject::State(string state)
  {
   m_state=state;
  }
void Subject::Notify(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      m_observers[i].Update(m_state);
     }
  }
void Subject::Attach(Observer *observer)
  {
   int size=ArraySize(m_observers);
   ArrayResize(m_observers,size+1);
   m_observers[size]=observer;
  }
void Subject::Detach(Observer &observer)
  {
   int find=Find(observer);
   if(find==-1)
      return;
   Observer* item=m_observers[find];
   if(CheckPointer(item)==1)
      delete item;
   ArrayRemove(m_observers,find,1);
  }
int Subject::Find(Observer &observer)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(item==&observer)
         return i;
     }
   return -1;
  }

Verwendung des Schlüsselworts class zur Deklaration des ConcreteSubject:

class ConcreteSubject:public Subject
  {
  public:
   void              State(string state);
   string            State(void) {return m_state;}
  };
void ConcreteSubject::State(string state)
  {
   m_state=state;
  } 

Verwendung des Schlüsselworts class zur Deklaration des ConcreteObserver:

class ConcreteObserver:public Observer
  {
public:
   void              Update(string state);
                     ConcreteObserver(ConcreteSubject& subject);
protected:
   string            m_observer_state;
   ConcreteSubject*  m_subject;
  };
ConcreteObserver::ConcreteObserver(ConcreteSubject& subject):
   m_subject(&subject)
  {
  }
void ConcreteObserver::Update(string state)
  {
   m_observer_state=state;
  }

Im Folgenden finden Sie also den vollständigen Code für die Verwendung des Observer-Verhaltensmusters in MQL5:

//+------------------------------------------------------------------+
//|                                                     Observer.mqh |
//+------------------------------------------------------------------+
interface Observer
  {
   void Update(string state);
  };
class Subject
  {
public:
                     Subject(void);
                    ~Subject(void);
   void              Attach(Observer* observer);
   void              Detach(Observer& observer);
   void              Notify(void);
   void              State(string state);
   string            State(void) {return m_state;}

protected:
   string            m_state;
   Observer*         m_observers[];

   int               Find(Observer& observer);
  };
Subject::Subject(void):
   m_state(NULL)
  {
  }
Subject::~Subject(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(CheckPointer(item)==1)
        {
         delete item;
        }
     }
  }
void Subject::State(string state)
  {
   m_state=state;
  }
void Subject::Notify(void)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      m_observers[i].Update(m_state);
     }
  }
void Subject::Attach(Observer *observer)
  {
   int size=ArraySize(m_observers);
   ArrayResize(m_observers,size+1);
   m_observers[size]=observer;
  }
void Subject::Detach(Observer &observer)
  {
   int find=Find(observer);
   if(find==-1)
      return;
   Observer* item=m_observers[find];
   if(CheckPointer(item)==1)
      delete item;
   ArrayRemove(m_observers,find,1);
  }
int Subject::Find(Observer &observer)
  {
   int itotal=ArraySize(m_observers);
   for(int i=0; i<itotal; i++)
     {
      Observer* item=m_observers[i];
      if(item==&observer)
         return i;
     }
   return -1;
  }
class ConcreteSubject:public Subject
  {
  public:
   void              State(string state);
   string            State(void) {return m_state;}
  };
void ConcreteSubject::State(string state)
  {
   m_state=state;
  }  
class ConcreteObserver:public Observer
  {
public:
   void              Update(string state);
                     ConcreteObserver(ConcreteSubject& subject);
protected:
   string            m_observer_state;
   ConcreteSubject*  m_subject;
  };
ConcreteObserver::ConcreteObserver(ConcreteSubject& subject):
   m_subject(&subject)
  {
  }
void ConcreteObserver::Update(string state)
  {
   m_observer_state=state;
  }

State

In diesem Teil werden wir das Zustandsmuster identifizieren, das es dem Objekt ermöglicht, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert, und das Objekt wird so aussehen, als ob es seine Klasse ändert. Es ist auch als „Objects for States“ bekannt. Wir können es verwenden, wenn das Verhalten des Objekts von seinem Zustand abhängt, indem wir das Verhalten des Objekts basierend auf dem Zustand zur Laufzeit ändern. Es kann auch verwendet werden, wenn wir Operationen mit großen bedingten Anweisungen haben und all das hängt vom Zustand des Objekts ab.

Was bewirkt das Muster?

Wenn wir verstehen wollen, wie das Verhaltensmuster des Staates funktioniert, können wir dies anhand des folgenden Diagramms sehen:

State

Wie aus dem vorherigen Diagramm hervorgeht, gibt es folgende Teilnehmer:

Context: Er identifiziert die Schnittstelle, die für den Client von Interesse ist. Er verwaltet auch die Unterklasse der Instanz von ConcreteState, die den aktuellen Zustand identifiziert.

State: Er bezeichnet die Schnittstelle, die das Verhalten eines bestimmten Zustands des Kontexts kapselt.

ConcreteState-Unterklassen: Die Unterklasse führt das Verhalten des Zustands des Kontexts aus, und zwar für jede Unterklasse.

Nach dem, was wir über das Zustandsmuster gesagt haben, kann es verwendet werden, wenn wir ein Objekt haben, das sich je nach seinem aktuellen Zustand unterschiedlich verhält. Aber es gibt einen Fallstrick bei der Verwendung dieses Musters ist, dass wir mehr Klassen, die mehr Code bedeutet haben wird.

Was löst das Entwurfsmuster?

Obwohl es die gleichen Fallstricke gibt, die wir bei der Verwendung des Zustandsmusters dargestellt haben, gibt es auch Vorteile, die als Probleme betrachtet werden können, die bei der Verwendung dieses Musters gelöst werden können. Dessen Vorteile sind:

  • Es hilft, das Verhalten zu lokalisieren, das für jeden Zustand spezifisch ist, und trennt das Verhalten auf der Grundlage der Merkmale jedes Zustands.
  • Es definiert ausdrücklich die Übergänge zwischen den verschiedenen Zuständen.
  • Es ist hilfreich, Statusobjekte gemeinsam zu nutzen.

Wie können wir sie in MQL5 verwenden?

Die folgenden Schritte sind für eine Methode, wie wir das State-Muster in MQL5 verwenden können:

Deklarieren der Context-Klasse:

class Context;

Deklarieren der Schnittstelle State:

interface State
  {
   void Handle(Context& context);
  };

Deklarieren des Objekts m_state:

State*            m_state;

Deklarieren der Context-Klasse:

class Context
  {
public:
                     Context(State& state);
                    ~Context(void);
   State*            State(void) {return m_state;}
   void              State(State& state);
   void              Request(void);
  };
Context::~Context(void)
  {
   if(CheckPointer(m_state)==1)
      delete m_state;
  }
void Context::State(State& state)
  {
   delete m_state;
   m_state=&state;
  }
void Context::Request(void)
  {
   m_state.Handle(this);
  }

Deklarieren der Klasse ConcreteStateA:

class ConcreteStateA:public State
  {
public:
   void              Handle(Context& context);
  };
void ConcreteStateA::Handle(Context& context)
  {
   context.State(new ConcreteStateB);
  }

Deklarieren der Klasse ConcreteStateB:

class ConcreteStateB:public State
  {
public:
   void              Handle(Context& context);
  };
void ConcreteStateB::Handle(Context& context)
  {
   context.State(new ConcreteStateA);
  }

Im Folgenden finden Sie also einen Codeblock, der das Entwurfmuster State in MQL5 umsetzt:

//+------------------------------------------------------------------+
//|                                                        State.mqh |
//+------------------------------------------------------------------+
class Context;
interface State
  {
   void Handle(Context& context);
  };
State*            m_state;
class Context
  {
public:
                     Context(State& state);
                    ~Context(void);
   State*            State(void) {return m_state;}
   void              State(State& state);
   void              Request(void);
  };
Context::~Context(void)
  {
   if(CheckPointer(m_state)==1)
      delete m_state;
  }
void Context::State(State& state)
  {
   delete m_state;
   m_state=&state;
  }
void Context::Request(void)
  {
   m_state.Handle(this);
  }
  
class ConcreteStateA:public State
  {
public:
   void              Handle(Context& context);
  };
void ConcreteStateA::Handle(Context& context)
  {
   context.State(new ConcreteStateB);
  }

class ConcreteStateB:public State
  {
public:
   void              Handle(Context& context);
  };
void ConcreteStateB::Handle(Context& context)
  {
   context.State(new ConcreteStateA);
  }

Strategy

Hier ist ein weiteres Verhaltensmuster, das Strategiemuster. Es identifiziert eine Gruppe von Algorithmen, kapselt sie ein und macht sie austauschbar. Dadurch kann der Algorithmus unabhängig von den Clients, die ihn verwenden, variieren. Sie kann also verwendet werden, wenn der Algorithmus zur Laufzeit ausgewählt werden soll, und wenn bedingte Anweisungen eliminiert werden sollen. Sie ist auch als Politik bekannt.

Wir können also sagen, dass wir das Strategiemuster verwenden können, wenn:

  • Viele verwandte Klassen unterscheiden sich nur in ihrem Verhalten. Strategien bieten eine Methode, um eine Klasse so zu konfigurieren, dass sie sich auf eine von vielen Arten verhält.
  • Wir müssen verschiedene Varianten desselben Algorithmus verwenden, und wir müssen die Möglichkeit haben, den Algorithmus zur Laufzeit auszuwählen.
  • Ein Algorithmus ist eine Verwendung von Daten, auf die Clients keinen Zugriff haben sollten.
  • Eine Klasse definiert viele Verhaltensweisen, die als mehrere bedingte Anweisungen in ihren Operationen erscheinen. Anstatt viele zusammenhängende bedingte Verzweigungen zu haben, kann sie in einer eigenen Strategieklasse untergebracht werden.
  • Eliminierung von bedingten Anweisungen.

Was bewirkt das Muster?

Wenn wir verstehen wollen, wie dieses Strategiemuster funktioniert, können wir das anhand des folgenden Diagramms tun:

Strategy

Wie aus dem vorstehenden Schaubild hervorgeht, hat das Strategiemuster die folgenden Teilnehmer:

  • Strategy: Sie deklariert die gemeinsame Schnittstelle für alle unterstützten Algorithmen, die vom Kontext verwendet werden soll, um den durch die ConcreteStrategy identifizierten Algorithmus aufrufen zu können.
  • ConcreteStrategy: Durch die Verwendung der Schnittstelle der Strategie implementiert Context den Algorithmus.
  • Context: Er wird vom ConcreteStrategy-Objekt erstellt, verwaltet den Verweis auf das Strategieobjekt und kann die Schnittstelle identifizieren, über die die Strategie auf ihre Daten zugreifen kann.

Obwohl dieses Strategiemuster Vorteile bietet, gibt es auch Fallstricke, die im Folgenden beschrieben werden:

  • Der Client muss über Strategien Bescheid wissen.
  • Die Zahl der Klassen ist gestiegen.

Was löst das Entwurfsmuster?

Im Folgenden werden die Folgen oder Vorteile der Verwendung des Strategiemusters beschrieben:

  • Sie hilft bei der Anwendung der Wiederverwendbarkeit, weil sie hilft, Gruppen verwandter Algorithmen zu identifizieren, die im Kontext wiederverwendet werden können.
  • Es kann als eine weitere Möglichkeit angesehen werden, eine Vielzahl von Verhaltensweisen anstelle von Unterklassen zu unterstützen.
  • Es hilft als Strategie, bedingte Anweisungen zu eliminieren.
  • Sie ermöglicht es, zwischen verschiedenen Implementierungen desselben Verhaltens zu wählen.

Wie können wir sie in MQL5 verwenden?

Nun werden wir einen Weg finden, das Strategiemuster zu verwenden, der den folgenden Schritten entspricht:

Deklarieren der Strategy-Schnittstelle, um Klassen und Funktionen darin zu deklarieren:

interface Strategy
  {
   void AlgorithmInterface(void);
  };

Deklarieren der Context-Klasse:

class Context
  {
public:
                     Context(Strategy& strategy);
                    ~Context(void);

   void              ContextInterface(void);
protected:
   Strategy*         m_strategy;
  };
Context::Context(Strategy& strategy)
  {
   m_strategy=&strategy;
  }
Context::~Context(void)
  {
   if(CheckPointer(m_strategy)==1)
      delete m_strategy;
  }
void Context::ContextInterface(void)
  {
   m_strategy.AlgorithmInterface();
  }

Deklarieren der Klasse ConcreteStrategyA:

class ConcreteStrategyA : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyA::AlgorithmInterface(void)
  {
  }

Deklarieren der Klasse ConcreteStrategyB:

class ConcreteStrategyB : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyB::AlgorithmInterface(void)
  {
  }

Deklarieren der Klasse ConcreteStrategyC:

class ConcreteStrategyC : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyC::AlgorithmInterface(void)
  {
  }  

Der folgende Code ist also der vollständige Code in einem Block zur Verwendung des Strategiemusters in MQL5:

//+------------------------------------------------------------------+
//|                                                     Strategy.mqh |
//+------------------------------------------------------------------+
interface Strategy
  {
   void AlgorithmInterface(void);
  };
class Context
  {
public:
                     Context(Strategy& strategy);
                    ~Context(void);

   void              ContextInterface(void);
protected:
   Strategy*         m_strategy;
  };
Context::Context(Strategy& strategy)
  {
   m_strategy=&strategy;
  }
Context::~Context(void)
  {
   if(CheckPointer(m_strategy)==1)
      delete m_strategy;
  }
void Context::ContextInterface(void)
  {
   m_strategy.AlgorithmInterface();
  }
class ConcreteStrategyA : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyA::AlgorithmInterface(void)
  {
  }
class ConcreteStrategyB : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyB::AlgorithmInterface(void)
  {
  }
class ConcreteStrategyC : public Strategy
  {
public:
   void              AlgorithmInterface(void);
  };
void ConcreteStrategyC::AlgorithmInterface(void)
  {
  }  

Template-Methode

Die Template-Methode ist ein weiteres verhaltensorientiertes Entwurfsmuster, das bei der Softwareentwicklung nützlich sein kann. Sie kann verwendet werden, um die grundlegenden Komponenten eines Algorithmus innerhalb einer Operation zu erkennen und bestimmte Schritte an Unterklassen zu delegieren. Dadurch können Unterklassen diese Schritte neu definieren, ohne die Gesamtstruktur des Algorithmus zu ändern.

Wir können also sagen, dass wir es verwenden können, wenn:

  • wir einen Algorithmus haben, von dem wir nur einige Schritte ändern müssen, ohne die Gesamtstruktur des Algorithmus zu beeinträchtigen,
  • wir die unveränderlichen Aspekte eines Algorithmus nur einmal implementieren müssen und die Verantwortung für die Implementierung des Verhaltens von Variablen an Unterklassen delegieren,
  • wir Code-Duplizierung ausschließen müssen, wenn das Verhalten, das den Unterklassen gemeinsam ist, in einer gemeinsamen Klasse berücksichtigt und lokalisiert werden soll,
  • wir die Erweiterungen der Unterklassen kontrollieren müssen.

Was bewirkt das Muster?

Wenn wir verstehen wollen, wie dieses Muster funktioniert, können wir dies mit Hilfe des folgenden Diagramms tun, das eine Darstellung des Musters ist:

 Template Method

Wie aus dem vorherigen Diagramm hervorgeht, gibt es folgende Teilnehmer:

  • AbstractClass: Die Klasse kann verwendet werden, um abstrakte primitive Operationen anzugeben, die von konkreten Unterklassen definiert werden müssen, um die verschiedenen Schritte eines Algorithmus zu implementieren. Sie kann auch dazu verwendet werden, eine Template-Method auszuführen, die den Rahmen eines Algorithmus umreißt. Diese Template Method ruft sowohl primitive Operationen als auch Operationen auf, die in AbstractClass angegeben sind oder zu anderen Objekten gehören.
  • ConcreteClass: Sie kann verwendet werden, um die primitiven Operationen auszuführen, die für die Durchführung der Schritte des Algorithmus der Unterklasse erforderlich sind.

Was löst das Entwurfsmuster?

Es gibt folgende Vorteile und Probleme, die bei der Verwendung des Verhaltensmusters der Template-Method gelöst werden können:

  • Die Verwendung der Template Method ermöglicht es uns, wiederverwendbaren Code zu erstellen, da diese Methoden eine grundlegende Technik für die Wiederverwendung von Code sind, insbesondere in Klassenbibliotheken.
  • So können wir die Schritte des Algorithmus ändern, ohne die Struktur des Algorithmus zu verändern.

Trotz dieser Vorteile gibt es einen Fallstrick bei der Verwendung dieses Musters, nämlich dass alle Klassen dem Algorithmus ohne Ausnahmen folgen müssen.

Wie können wir sie in MQL5 verwenden?

Wenn wir das Muster der Template Method in MQL5 verwenden müssen, können wir dies durch die folgenden Schritte als Methode tun:

Deklarieren von AbstractClass mit dem Schlüsselwort class:

class AbstractClass
  {
public:
   virtual void      PrimitiveOperation1(void)=0;
   virtual void      PrimitiveOperation2(void)=0;
   virtual void      TemplateMethod(void);
  };
void AbstractClass::TemplateMethod(void)
  {
   PrimitiveOperation1();
   PrimitiveOperation2();
  }

Deklarieren von ConcreteClass mit dem Schlüsselwort class:

  class ConcreteClass : public AbstractClass
  {
public:
   void              PrimitiveOperation1(void);
   void              PrimitiveOperation2(void);
  };
void ConcreteClass::PrimitiveOperation1(void)
  {
  }
void ConcreteClass::PrimitiveOperation2(void)
  {
  }

Der folgende Codeblock ist also der vollständige Code zur Verwendung des Musters „Template Method“ in MQL5:

//+------------------------------------------------------------------+
//|                                              Template_Method.mqh |
//+------------------------------------------------------------------+
class AbstractClass
  {
public:
   virtual void      PrimitiveOperation1(void)=0;
   virtual void      PrimitiveOperation2(void)=0;
   virtual void      TemplateMethod(void);
  };
void AbstractClass::TemplateMethod(void)
  {
   PrimitiveOperation1();
   PrimitiveOperation2();
  }
  class ConcreteClass : public AbstractClass
  {
public:
   void              PrimitiveOperation1(void);
   void              PrimitiveOperation2(void);
  };
void ConcreteClass::PrimitiveOperation1(void)
  {
  }
void ConcreteClass::PrimitiveOperation2(void)
  {
  }

Visitor

Das letzte Verhaltensmuster ist das Visitor Pattern (Besuchermuster). Es ermöglicht, eine neue Operation zu identifizieren, ohne dass dies Auswirkungen auf die Klassen der Elemente hat, an denen die Operation durchgeführt wird. Wir können dieses Muster verwenden, wenn wir eine Objektstruktur haben, die viele unterschiedliche Klassen von Objekten und Schnittstellen enthält, und wir Operationen an diesen Objekten innerhalb der Objektstruktur ausführen müssen. Dazu besteht der Wunsch, die „Verschmutzung“ ihrer Klassen durch diese Operationen zu vermeiden. So werden die Klassen, die die Struktur des Objekts definieren, selten geändert, aber wir müssen dafür neue Operationen für die Struktur definieren.

Was bewirkt das Muster?

In der folgenden Grafik können wir sehen, wie das Muster der Besucher (visitor) durch seine Struktur wirkt:

Visitor

Wie aus dem vorherigen Diagramm hervorgeht, gibt es folgende Teilnehmer:

  • Visitor: für jede Klasse von ConcreteElement in der Objektstruktur wird eine Operation „Visit“ deklariert. Der Name und die Signatur der Operation identifizieren eindeutig die Klasse, die die Anfrage zum Besuch des Besuchers sendet. Dieses Design ermöglicht den direkten Zugriff auf das Element über seine spezifische Schnittstelle, sodass der Besucher die konkrete Klasse des besuchten Elements bestimmen kann.
  • ConcreteVisitor: Er führt die Implementierung jeder vom Besucher angegebenen Operation durch. Jede Operation implementiert ein Fragment des Algorithmus, der für die entsprechende Klasse des Objekts in der Struktur definiert ist. Der konkrete Besucher liefert den Kontext für den Algorithmus und speichert seinen lokalen Zustand. Dieser Zustand ist oft eine Ansammlung von Ergebnissen während der Iteration durch die Struktur.
  • Element: gibt eine akzeptierende Operation an, die einen Besucher als Argument annimmt.
  • ConcreteElement: Es führt eine akzeptierende Operation aus, die den Besucher als Argument annimmt.
  • ObjectStructure: Sie kann ihre Elemente auflisten, sie kann eine High-Level-Schnittstelle bereitstellen, die es dem Besucher ermöglicht, ihre Elemente zu besuchen, und sie kann, wie eine Liste oder eine Menge, entweder ein Kompositum oder eine Sammlung sein.

Was löst das Entwurfsmuster?

Nach dem, was wir über die Besucher-Muster können wir feststellen, dass es u.a. folgenden Vorteile hat bzw. Probleme löst:

  • Es macht das Hinzufügen neuer Vorgänge sehr einfach.
  • Es ermöglicht die Unterscheidung zwischen verwandten und nicht verwandten Vorgängen, da sie verwandte Vorgänge zusammenfasst und von den nicht verwandten Vorgänge trennt.

Wie können wir sie in MQL5 verwenden?

Wenn wir das Visitor-Muster in MQL5 verwenden müssen, sind die folgenden Schritte eine Methode, dies zu tun:

Deklarieren der Schnittstelle Visitor:

interface Visitor;

Deklarieren der Klasse Element:

class Element
  {
protected:
   Visitor*          m_visitor;
public:
                    ~Element(void);
   virtual void      Accept(Visitor* visitor)=0;
protected:
   void              Switch(Visitor* visitor);
  };
Element::~Element(void)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
  }
void Element::Switch(Visitor *visitor)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
   m_visitor=visitor;
  }

Deklarieren der Klasse ConcreteElementA:

class ConcreteElementA : public Element
  {
public:
   void              Accept(Visitor*);
   void              OperationA(void);
  };
void ConcreteElementA::OperationA(void)
  {
  }
void ConcreteElementA::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementA(&this);
  }

Deklarieren der Klasse ConcreteElementB

class ConcreteElementB : public Element
  {
public:
   void              Accept(Visitor* visitor);
   void              OperationB(void);
  };
void ConcreteElementB::OperationB(void)
  {
  }
void ConcreteElementB::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementB(&this);
  }

Verwendung des Schlüsselworts interface zur Deklaration des Visitor, um innerhalb VisitElementA und VisitElementB zu deklarieren:

interface Visitor
{
   void VisitElementA(ConcreteElementA*);
   void VisitElementB(ConcreteElementB*);
};

Deklarieren der Klasse ConcreteVisitor1:

class ConcreteVisitor1 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA* visitor);
   void              VisitElementB(ConcreteElementB* visitor);
  };
void ConcreteVisitor1::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor1::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }

Deklarieren der Klasse ConcreteVisitor2:

class ConcreteVisitor2 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA*);
   void              VisitElementB(ConcreteElementB*);
  };
void ConcreteVisitor2::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor2::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }

Der folgende Codeblock ist also der vollständige Code, um das Besuchermuster in MQL5 zu sehen:

//+------------------------------------------------------------------+
//|                                                      Visitor.mqh |
//+------------------------------------------------------------------+
interface Visitor;
class Element
  {
protected:
   Visitor*          m_visitor;
public:
                    ~Element(void);
   virtual void      Accept(Visitor* visitor)=0;
protected:
   void              Switch(Visitor* visitor);
  };
Element::~Element(void)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
  }
void Element::Switch(Visitor *visitor)
  {
   if(CheckPointer(m_visitor)==1)
      delete m_visitor;
   m_visitor=visitor;
  }
class ConcreteElementA : public Element
  {
public:
   void              Accept(Visitor*);
   void              OperationA(void);
  };
void ConcreteElementA::OperationA(void)
  {
  }
void ConcreteElementA::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementA(&this);
  }
class ConcreteElementB : public Element
  {
public:
   void              Accept(Visitor* visitor);
   void              OperationB(void);
  };
void ConcreteElementB::OperationB(void)
  {
  }
void ConcreteElementB::Accept(Visitor *visitor)
  {
   Switch(visitor);
   visitor.VisitElementB(&this);
  }
interface Visitor
  {
   void VisitElementA(ConcreteElementA*);
   void VisitElementB(ConcreteElementB*);
  };
class ConcreteVisitor1 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA* visitor);
   void              VisitElementB(ConcreteElementB* visitor);
  };
void ConcreteVisitor1::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor1::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }
class ConcreteVisitor2 : public Visitor
  {
public:
   void              VisitElementA(ConcreteElementA*);
   void              VisitElementB(ConcreteElementB*);
  };
void ConcreteVisitor2::VisitElementA(ConcreteElementA* visitor)
  {
   visitor.OperationA();
  }
void ConcreteVisitor2::VisitElementB(ConcreteElementB* visitor)
  {
   visitor.OperationB();
  }

Schlussfolgerung

Nach der Lektüre dieses und anderer Artikel in dieser Reihe werden Sie in der Lage sein, alle Arten von Entwurfsmustern zu erkennen (kreative, strukturelle und verhaltensbezogene). Wir haben jedes Muster vorgestellt, indem wir erklärt haben, wie es jeweils funktioniert, wir es verwendet werden kann, welche Fragen oder Probleme die Verwendung von den einzelnen Mustern löst und wie diese Muster in MQL5 verwendet werden können, um sauberen Code zu schreiben, was bedeutet, dass wir wartbaren, wiederverwendbaren und gut testbaren Code erhalten.

Wir haben Muster folgende Muster besprochen:

  • Erzeugungsmuster
    • Abstrakte Fabrik
    • Builder
    • Fabrikmethode
    • Prototyp
    • Singleton
  • Strukturelle Muster
    • Adapter
    • Bridge
    • Composite
    • Decorator
    • Facade
    • Flyweight
    • Proxy
  • Verhaltensmuster
    • Kette der Verantwortung
    • Anweisung
    • Interpreter
    • Iterator
    • Mediator
    • Memento
    • Observer
    • State
    • Template
    • Visitor

Sie können die anderen Artikel dieser Serie lesen, indem Sie auf die unten stehenden Links klicken:

Ich kann nicht genug betonen, wie wichtig es ist, das Thema Entwurfmuster zu verstehen, da es, wie bereits erwähnt, bei der Erstellung von Software sehr nützlich sein kann. Daher empfehle ich, mehr zu diesem Thema zu lesen und die folgenden Referenzen zu diesem Thema zu lesen:

  • Design Patterns - Elements of Reusable Object-Oriented Software by Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides
  • Design Patterns for Dummies by Steve Holzner
  • Head First Design Patterns by Eric Freeman, Elisabeth Robson, Bert Bates, and Kathy Sierra

Ich hoffe, dass Sie diesen Artikel und diese Serie als nützlich für Ihre Reise zur Verbesserung Ihrer Programmierkenntnisse im Allgemeinen und von MQL5 im Besonderen empfunden haben. Wenn Sie mehr Artikel über die Programmierung von MQL5 lesen möchten und darüber, wie Sie Handelssysteme erstellen können, die auf den beliebtesten technischen Indikatoren wie gleitenden Durchschnitten, RSI, Bollinger Bands und MACD basieren, können Sie meine Publikationsseite besuchen und ich hoffe, dass Sie sie nützlich finden, um Ihr Handels- und Programmierwissen zu verbessern.

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

Beigefügte Dateien |
Memento.mqh (1.34 KB)
Observer.mqh (2.5 KB)
State.mqh (1.19 KB)
Strategy.mqh (1.24 KB)
Visitor.mqh (2.14 KB)
Entwicklung eines Wiedergabesystems — Marktsimulation (Teil 21): FOREX (II) Entwicklung eines Wiedergabesystems — Marktsimulation (Teil 21): FOREX (II)
Wir werden weiterhin ein System für die Arbeit auf dem FOREX-Markt aufbauen. Um dieses Problem zu lösen, müssen wir zuerst das Laden der Ticks deklarieren, bevor wir die vorherigen Balken laden. Dies löst zwar das Problem, zwingt den Nutzer aber gleichzeitig dazu, sich an eine bestimmte Struktur in der Konfigurationsdatei zu halten, was ich persönlich nicht sehr sinnvoll finde. Der Grund dafür ist, dass wir durch die Entwicklung eines Programms, das für die Analyse und Ausführung der Konfigurationsdatei verantwortlich ist, dem Nutzer die Möglichkeit geben können, die von ihm benötigten Elemente in beliebiger Reihenfolge zu deklarieren.
MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 09): K-Means-Clustering mit fraktalen Wellen MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 09): K-Means-Clustering mit fraktalen Wellen
Das K-Means-Clustering verfolgt den Ansatz, Datenpunkte als einen Prozess zu gruppieren, der sich zunächst auf die Makroansicht eines Datensatzes konzentriert und zufällig generierte Clusterzentren verwendet, bevor er heranzoomt und diese Zentren anpasst, um den Datensatz genau darzustellen. Wir werden uns dies ansehen und einige Anwendungsfälle ausnutzen.
Algorithmen zur Optimierung mit Populationen: Stochastische Diffusionssuche (SDS) Algorithmen zur Optimierung mit Populationen: Stochastische Diffusionssuche (SDS)
Der Artikel behandelt die stochastische Diffusionssuche (SDS), einen sehr leistungsfähigen und effizienten Optimierungsalgorithmus, der auf den Prinzipien des Random Walk basiert. Der Algorithmus ermöglicht es, optimale Lösungen in komplexen mehrdimensionalen Räumen zu finden, wobei er sich durch eine hohe Konvergenzgeschwindigkeit und die Fähigkeit auszeichnet, lokale Extrema zu vermeiden.
Filterung und Merkmalsextraktion von Frequenzen Filterung und Merkmalsextraktion von Frequenzen
In diesem Artikel untersuchen wir die Anwendung digitaler Filter auf Zeitreihen, die im Frequenzbereich dargestellt werden, um einzigartige Merkmale zu extrahieren, die für Vorhersagemodelle nützlich sein können.