English Русский Deutsch
preview
ソフトウェア開発とMQL5におけるデザインパターン(第4回):振る舞いパターン2

ソフトウェア開発とMQL5におけるデザインパターン(第4回):振る舞いパターン2

MetaTrader 5トレーディング | 22 3月 2024, 11:37
245 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

はじめに

この記事では、ソフトウェアにおけるデザインパターンのトピックと、MQL5でそれらをどのように使用できるかについての説明を完成させるために、振る舞いデザインパターンの説明を完成させます。以前の「ソフトウェア開発とMQL5におけるデザインパターン(第1回):生成パターン」稿では、すべての生成パターンを特定しました。「ソフトウェア開発とMQL5におけるデザインパターン(第2回):構造パターン」稿では、すべての構造パターンを特定しました。そして、前回の「ソフトウェア開発とMQL5におけるデザインパターン(第3回):振る舞いパターン1」稿では、振る舞いパターンの一部(Chain of responsibility、Command、Interpreter、 Iterator、Mediator)をカバーしました。また、オブジェクト間の通信方法を定義し、管理するために使用できるパターンであるデザインパターンとは何かを確認しました。

今回は、以下のトピックを通して、残った振る舞いパターンの説明を完成させます。

  • Memento:カプセル化に違反することなく、オブジェクトの内部状態をキャプチャして外部化することで、オブジェクトを保存された状態に復元するために使用できる
  • Observer:オブジェクト間の1対多の依存関係を定義するために使用できます。1つのオブジェクトがその状態を変更すると、すべての依存関係が通知を受け取り、自動的に更新される
  • State:オブジェクトの内部状態が変化したときに、そのオブジェクトの振る舞いを変更させるために使用できる。オブジェクトのクラスが変更されたように見える
  • Strategy:アルゴリズムのファミリーを識別し、それらをカプセル化し、互換性を持たせるために使用できます。このStrategyにより、アルゴリズムはそれを使用するクライアントとは無関係に変化します。
  • Template Method:アルゴリズムの構造を変更することなく、サブクラスがそれらを再識別できるようにすることで、いくつかのステップをサブクラスに残して、操作のアルゴリズムの基本を識別するために使用できる
  • Visitor:操作が実行される要素のクラスに影響を与えることなく、新しい操作を識別するために使用できる
  • 結論

振る舞いパターンとは、オブジェクト間の責任の分担と決定に関係する振る舞いのパターンであることは先に述べました。また、オブジェクトが互いにどのように通信したり、相互作用したりするのかも特定します。 これらは、実行時にフォローするのが難しい複雑な制御の流れを特徴づけます。オブジェクトがどのように接続されているかにのみ集中することができ、制御の流れからフォーカスをずらすことができます。

この連載の過去の記事をお読みになった方なら、それぞれのパターンを以下のポイントを通して紹介するアプローチにお気づきでしょう。

  • パターンの役割
  • パターンが解決する問題
  • MQL5での使用方法

OOP(オブジェクト指向プログラミング)のトピックを理解することがデザインパターンのトピックをよく理解するのに役立つことを言及するのは重要です。このトピックの一部を読んだり学んだりしたい場合、私が以前書いた「MQL5オブジェクト指向プログラミング(OOP)について」をお読みください。クリーンで、拡張可能かつ再利用可能で、十分にテストされたコードを書くためのデザインパターンという最も重要なトピックの1つを特定することで、私の記事が読者のプログラミングスキルを向上させる旅に役立つことを願っています。

免責条項:すべての情報は「現状有姿」で提供され、情報提供のみを目的としており、取引目的やアドバイスを目的としたものではありません。いかなる結果も保証するものではありません。読者がこれらの資料を自分の取引口座で使用する場合、自己責任でおこなってください。

Memento

このセクションでは、Memento振る舞いデザインパターンについて説明します。Mementoパターンは、ロールバック機能を提供するためにオブジェクトの状態を外部化するために使用することができ、トークンとしても知られています。

パターンの役割

Mementoパターンを使用できるのは、オブジェクトの状態のスナップショットを保存して後で復元する必要があるときや、状態を取得するための直接的なインターフェイスが実行の詳細を暴露してしまい、オブジェクトのカプセル化を壊してしまうようなときです。このパターンは、オブジェクトの状態をキャプチャして外部化し、後で復元できるようにします。以下は、このパターンがどのように機能するかを示す構造図です。

Memento

前の図でわかるように、このパターンには次のような参加者がいます。

  • Memento:状態を保存するOriginatorオブジェクトの判断で、必要に応じて状態を保存する。Originator以外のオブジェクトへのアクセスは許可されない。Originatorの内部状態は、Originatorの判断で必要なだけ保存してもいいし、保存しなくてもよい。管理人またはOriginatorの視点に基づき、narrowまたはwideの2つのインターフェイスがある
  • Originator:現在の内部状態のスナップショットを含むMementoを作成し、Mementoを使用して内部状態を復元する
  • Caretaker:Mementoの内容を調べることなく、Mementoを保存する責任がある

このタイプのパターンの使用には以下に挙げるようないくつかの落とし穴があります。

  • コピー数が多いと高くつくことがある
  • スナップショットのスロットには限りがあるため履歴が損失される
  • Memento以外の情報を公開しない

デザインパターンが解決する問題

このパターンを使用することで、以下のような問題を解決することができます。

  • カプセル化の境界を維持する
  • 狭いインターフェイスと広いインターフェイスを識別する
  • Originatorを単純化する

MQL5での使用方法

このセクションでは、MQL5でMementoパターンを使ってみることにします。次の手順を実行できます。

classキーワードを使用して、Mementoクラスを宣言します。

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;
  }

Originatorクラスを宣言します。

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;
  }

Caretakerクラスを宣言します。

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

MQL5でMementoパターンを使用するための完全なコードは次のように1つのブロックになります。

//+------------------------------------------------------------------+
//|                                                      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

このセクションでは、もう1つの振る舞いパターンであるObserverパターンについて説明します。このパターンは、オブジェクト間の1対多の依存関係を定義し、あるオブジェクトが状態を変更すると、その依存関係すべてが通知され、自動的に更新されるようにします。DependentやPublish-Subscribeとも呼ばれます。

パターンの役割

Observerパターンは、抽象化の側面を別々のオブジェクトにカプセル化し、それらを独立して変化させたり再利用できるようにする必要がある場合、1つのオブジェクトを変更するときに他のオブジェクトを変更する必要があり、変更する必要があるオブジェクトの数がわからない場合、オブジェクト間の結合なしに他のオブジェクトに通知を送信する必要がある場合などに使用できます。

以上のことから、このパターンがどのように機能するかをよりよく理解するために、このパターンを以下の図で表すことができます。

Observer

前の図でわかるように、このパターンには次のような参加者がいます。

  • Subject:そのObserverを特定し、そのサブジェクトが何人のObserverに観察されるかを特定する。Observerのオブジェクトを着脱するためのインターフェイスを備えている
  • Observer:サブジェクトの変更を通知する必要があるオブジェクトのための更新されたインターフェイスを識別する
  • ConcreteSubject:状態をConcreteObserverオブジェクトに保存または格納し、状態に変更があった場合、その通知をObserverに送信する
  • ConcreteObserver:以下の3つの目的を果たす役割を果たす
    • ConcreteSubjectのオブジェクトへの参照を維持
    • 状態を保存
    • Observer更新のインターフェイスを実行

Observerパターンを使用する場合、次のような落とし穴があります。

  • observableはどのオブジェクトが状態を更新したかを特定しない
  • 大規模なアップデート
  • デバッグが困難な可能性がある

デザインパターンが解決する問題

Observerパターンがどのように機能するのか私たちが理解していることによれば、Observerパターンを使用することには次のような利点があります。

  • SubjectとObserverの抽象的な結合が可能になる
  • ブロードキャストインタラクションや通信をサポートする

MQL5での使用方法

次にMQL5でObserverパターンを使用する方法を見てみましょう。

Interfaceキーワードを使ってObserver領域を宣言し、必要なクラスと関数を決定します。

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

classキーワードを使ってSubjectを宣言します。

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を宣言します。

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を宣言します。

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;
  }

MQL5でObserver振る舞いデザインパターンを使用するための完全なコードを以下に示します。

//+------------------------------------------------------------------+
//|                                                     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

このセクションでは、オブジェクトの内部状態が変化した場合にその振る舞いを変更してオブジェクトがそのクラスを変更したように見えるようにできる、Stateパターンについて説明します。「Objects for States」とも呼ばれます。実行時に状態に応じてオブジェクトの振る舞いを変えることで、オブジェクトの振る舞いが状態に依存する場合に使用することができます。また、大きな条件文を伴う操作があり、そのすべてがオブジェクトの状態に依存する場合にも使えます。

パターンの役割

State振る舞いパターンがどのように機能するかは、次の図を見ればわかります。

State

前の図でわかるように、このパターンには次のような参加者がいます。

Context:クライアントにとって関心のあるインターフェイスを識別し、現在の状態を識別するConcreteStateのインスタンスのサブクラスを保持

State:コンテキストの特定の状態の振る舞いをカプセル化するインターフェイスを識別

ConcreteStateサブクラス:コンテキストの状態の振る舞いを実行

Stateパターンについて述べたところによると、これは、現在の状態によって異なる振る舞いをするオブジェクトがある場合に使用することができます。しかし、このパターンを使用すると落とし穴があります。クラスが増えるのでコードが増えるということです。

デザインパターンが解決する問題

Stateパターンを使用することにはこれまで紹介したような落とし穴がありますが、使用することで解決できる問題点として考えられる利点もあります。これらの利点は以下の通りです。

  • 各状態に特有な振る舞いを局所化し、各状態の特徴に基づいて振る舞いを分離できるようにする
  • 異なる状態間の遷移を明確に定義する
  • 状態オブジェクトを共有できるようにする

MQL5での使用方法

次は、MQL5でStateパターンを使用するための手順です。

Contextクラスを宣言します。

class Context;

Stateインターフェイスを宣言します。

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

m_stateオブジェクトを宣言します。

State*            m_state;

Contextクラスを宣言します。

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);
  }

ConcreteStateAクラスを宣言します。

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

ConcreteStateBクラスを宣言します。

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

MQL5でStateデザインパターンを使用するための完全なコードは次のように1つのブロックになります。

//+------------------------------------------------------------------+
//|                                                        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

もう1つの振る舞いパターンはStrategyパターンです。 アルゴリズムの集団を特定し、それらをカプセル化し、互換性を持たせます。これにより、アルゴリズムを使用するクライアントとは無関係に、アルゴリズム変化させることができます。そのため、実行時にアルゴリズムを選択できるようにする必要があるときや、条件文を排除する必要があるときに使用することができます。Policy(方策)とも呼ばれます。

つまり、Strategyパターンが使用できるのは次のような場合です。

  • 多くの関連クラスの唯一の違いが振る舞い方であり、Strategyはクラスが様々な振る舞いをするように設定するメソッドを提供する
  • 同じアルゴリズムの異なるバリエーションを使用する必要があり、実行時にアルゴリズムを選択する能力が必要である
  • アルゴリズムは、クライアントがアクセスすべきではないデータの使用である
  • クラスは多くの振る舞いを定義し、それらは操作の中で複数の条件文として現れ、多くの関連した条件分岐を持つよりも、別のStrategyクラスにすることができる
  • 条件文の排除

パターンの役割

Strategyパターンがどのように機能するかは、次の図を見ればわかります。

Strategy

前の図でわかるように、このパターンには次のような参加者がいます。

  • Strategy:ConcreteStrategyによって特定されるアルゴリズムを呼び出すことができるように、コンテキストによって使用されるすべてのサポートされるアルゴリズムに共通するインターフェイスを宣言
  • ConcreteStrategy:Strategyのインターフェイスを使用することで、コンテキストはアルゴリズムを実装
  • Context:ConcreteStrategyオブジェクトによって構築され、Strategyオブジェクトへの参照を保持し、Strategyがそのデータにアクセスするためのインターフェイスを特定することができる

このStrategyパターンを使用することで得られる利点もありますが、次のような落とし穴もあります。

  • クライアントはStrategyについて知らなければならない
  • クラス数が増える

デザインパターンが解決する問題

以下は、Strategyパターンを使用することによる結果や利点です。

  • コンテキストによって再利用可能な関連アルゴリズムの集団を識別できるようにして、再利用性を適用できるようにする
  • サブクラス化する代わりに、さまざまな動作をサポートするもうひとつの方法と考えることができる
  • 条件文を排除する戦略として役立つ
  • 同じ振る舞いに対して異なる実装を選択することができる

MQL5での使用方法

では、次の手順で、Strategyパターンの使い方を特定しましょう。

Strategyインターフェイスを宣言し、その中でクラスや関数を宣言します。

interface Strategy
  {
   void AlgorithmInterface(void);
  };

Contextクラスを宣言します。

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

ConcreteStrategyAクラスを宣言します。

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

ConcreteStrategyBクラスを宣言します。

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

ConcreteStrategyCクラスを宣言します。

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

MQL5でStrategyパターンを使用するための完全なコードは次のように1つのブロックになります。

//+------------------------------------------------------------------+
//|                                                     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 Method

Template Methodは、ソフトウェア開発に役立つもう1つの振る舞いデザインパターンです。 これは、操作内のアルゴリズムの基本コンポーネントを認識し、特定のステップをサブクラスに委譲するために使用できます。サブクラスはアルゴリズム全体の構造を変えることなく、これらのステップを再定義することができます。

よって、このパターンは次のときに使用できると言うことができます。

  • アルゴリズムが存在し、このアルゴリズムの全体的な構造に影響を与えることなく、そこからいくつかのステップだけを変更する必要がある
  • アルゴリズムの不変的な側面は一度だけ実装し、変数の動作の実装責任はサブクラスに委譲する必要がある
  • サブクラスに共通する動作は、共通のクラスで考慮され、ローカライズされるべきである
  • サブクラスの拡張をコントロールする必要がある

パターンの役割

このパターンがどのように機能するかを理解するには、パターンを表現した以下のチャートの助けを借りることができます。

 Template Method

前の図でわかるように、このパターンには次のような参加者がいます。

  • AbstractClass:アルゴリズムのさまざまなステップを実装するために、具体的なサブクラスによって定義されなければならない抽象的なプリミティブ操作を指定するために使用できる。また、アルゴリズムの骨格を示すTemplate Methodを実行するためにも使用できる。このTemplate Methodは、プリミティブ操作とAbstractClassで指定された操作、または他のオブジェクトに属する操作の両方を呼び出す
  • ConcreteClass: サブクラス固有のアルゴリズムのステップを実行するために必要なプリミティブ操作を実行するために使用できる

デザインパターンが解決する問題

Template Methodの振る舞いパターンを使用することで解決できる利点と問題点があり、それらは以下の通りです。

  • Template Methodを使用することで、再利用可能なコードを作成することができる(これらのメソッドは、特にクラスライブラリにおいて、コードを再利用するための基本的なテクニックである)
  • アルゴリズムの構造を変えることなく、ステップを変更することができる

このような利点があるにもかかわらず、このパターンを使用するときには、すべてのクラスが例外なくアルゴリズムに従わなければならないという落とし穴があります。

MQL5での使用方法

MQL5でTemplate Methodパターンを使用する必要がある場合、以下の手順でおこなうことができます。

classキーワードを使用してAbstractClassを宣言します。

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

クラスキーワードを使用してConcreteClassを宣言します。

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

MQL5でTemplate Methodパターンを使用するための完全なコードは次のように1つのブロックになります。

//+------------------------------------------------------------------+
//|                                              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

最後の振る舞いパターンであるVisitorパターンを紹介します。これにより、その操作が実行される要素のクラスに影響を与えることなく、新しい操作を特定することが可能になります。このパターンは、オブジェクト構造にさまざまなオブジェクトとインターフェイスの多くのクラスが含まれており、これらのオブジェクトに対して操作を実行する必要がある場合、オブジェクト構造内のオブジェクトに対して実行する必要があるさまざまで無関係な操作が多数ある場合、これらの操作によるクラスの「汚染」を回避したいという要望がある場合、オブジェクトの構造を定義するクラスはめったに変更されないが、その構造に対して新しい操作を定義する必要がない場合に使用できます。

パターンの役割

次の図では、Visitorパターンがその構造を通してどのように作用しているかを見ることができます。

Visitor

前の図でわかるように、このパターンには次のような参加者がいます。

  • Visitor:オブジェクト構造内のConcreteElementの各クラスに対して、Visit操作が宣言されている。操作の名前とシグネチャは、訪問者を訪問する要求を送信するクラスを一意に識別する。このデザインは、訪問者が訪問されている要素の具体的なクラスを決定できるように、その特定のインターフェイスを介して要素への直接アクセスを可能にする
  • ConcreteVisitor:Visitorによって宣言された各操作の実装を実行する。各操作は、構造体内のオブジェクトの対応するクラスに定義されているアルゴリズムの断片を実装している。具象Visitorは、アルゴリズムにコンテキストを提供し、そのローカル状態を保存する。この状態は、多くの場合、構造を反復する間の結果の蓄積である
  • Element:Visitorを引数として受け取る操作を指定する
  • ConcreteElement:Visitorを引数として受け取る操作を実行する
  • ObjectStructure:その要素をリストアップすることができ、訪問者がその要素を訪問できるように高レベルのインターフェイスを提供することができる

デザインパターンが解決する問題

Visitorパターンについて述べたことによると、このパターンを適用したり使用したりすることには次のような利点があります。

  • 新しい操作を追加するのがとても簡単になる
  • 関連性のあるものを集め、関連性のないものを分けるので、関連性のあるものとないものを区別することができる

MQL5での使用方法

MQL5でVisitorパターンを使用する必要がある場合、その方法は次のようになります。

Visitorインターフェイスを宣言します。

interface Visitor;

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;
  }

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);
  }

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);
  }

interfaceキーワードを使用してVisitorを宣言し、VisitElementAとVisitElementBを次のように定義します。

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

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

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

MQL5のVisitorパターンを見るための完全なコードは次のように1つのブロックになります。

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

結論

この記事とこの連載の他の記事を読めば、パターンであるデザインパターンのすべてのタイプ(創造、構造、振る舞い)を識別できるようになるでしょう。それぞれのパターンを理解することで、どのようなときに使用できるのか、それぞれのパターンを使用することでどのような問題や課題が解決できるのか、そしてMQL5でこれらのパターンをどのように使えばクリーンなコード(つまり、メンテナンス可能で、再利用可能で、十分にテストされたコード)が書けるのかを理解することで、それぞれのパターンを特定しました。

以下のようなパターンについて説明しました。

  • 生成パターン
    • Abstract Factory
    • Builder
    • Factory Method
    • Prototype
    • Singleton
  • 構造パターン
    • Adapter
    • Bridge
    • Composite
    • Decorator
    • Facade
    • Flyweight
    • Proxy
  • 振る舞いパターン
    • Chain of Responsibility
    • Command
    • Interpreter
    • Iterator
    • Mediator
    • Memento
    • Observer
    • State
    • Template
    • Visitor

本連載の他の記事は、以下のリンクからご覧になれます。

デザインパターンというトピックを理解することの重要性はいくら強調してもしすぎることはありません。なぜなら、これまで述べてきたように、デザインパターンはあらゆるソフトウェアの作成に非常に役立つためです。そのため、このトピックについてさらに読むことと、同じトピックに関する以下の参考文献を読むことをお勧めします。

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

この記事と連載が、読者の一般的なプログラミングスキル、特にMQL5のスキル向上の旅に役立つことを願っています。MQL5によるプログラミングや、移動平均、RSI、ボリンジャーバンド、MACDといった最も人気のあるテクニカル指標を例にした取引システムの作成方法に関する記事をもっとお読みになりたい場合は、私の出版物のページをご覧ください。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/13876

添付されたファイル |
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)
周波数領域でのフィルタリングと特徴抽出 周波数領域でのフィルタリングと特徴抽出
この記事では、予測モデルに有用な独自の特徴を抽出するために周波数領域で表現された時系列にデジタルフィルタを適用する方法を探ります。
データサイエンスと機械学習(第17回):木の中のお金?外国為替取引におけるランダムフォレストの芸術と科学 データサイエンスと機械学習(第17回):木の中のお金?外国為替取引におけるランダムフォレストの芸術と科学
金融情勢を解読する際の芸術性と正確性の融合についてガイドします。アルゴリズム錬金術の秘密を発見してください。ランダムフォレストがデータを予測能力に変換する方法を明らかにし、株式市場の複雑な地形をナビゲートするための独自の視点を提供します。金融の魔術の核心に触れ、市場の動向を形作り、収益の機会を開拓するランダムフォレストの役割を解き明かす旅にご参加ください。
知っておくべきMQL5ウィザードのテクニック(第09回):K平均法とフラクタル波の組み合わせ 知っておくべきMQL5ウィザードのテクニック(第09回):K平均法とフラクタル波の組み合わせ
K平均法では、まず無作為に生成されたクラスタ重心を使用するデータセットのマクロビューに焦点を当てたプロセスとしてデータポイントを集団化するアプローチを採用し、その後ズームインしてこれらの重心を調整してデータセットを正確に表現します。これを見て、その使用例をいくつか活用していきます。
データサイエンスと機械学習(第16回):決定木を見直す データサイエンスと機械学習(第16回):決定木を見直す
連載「データサイエンスと機械学習」の最新作で、決定木の複雑な世界に飛び込みましょう。戦略的な洞察を求めるトレーダーのために、この記事は包括的な総括として、市場動向の分析において決定木が果たす強力な役割に光を当てています。これらのアルゴリズム木の根と枝を探り、取引の意思決定を強化する可能性を解き明かします。決定木について新たな視点から学び、複雑な金融市場をナビゲートする上で、決定木をどのように味方にできるかを発見しましょう。