
ソフトウェア開発とMQL5におけるデザインパターン(第4回):振る舞いパターン2
はじめに
この記事では、ソフトウェアにおけるデザインパターンのトピックと、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:状態を保存する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つのオブジェクトを変更するときに他のオブジェクトを変更する必要があり、変更する必要があるオブジェクトの数がわからない場合、オブジェクト間の結合なしに他のオブジェクトに通知を送信する必要がある場合などに使用できます。
以上のことから、このパターンがどのように機能するかをよりよく理解するために、このパターンを以下の図で表すことができます。
前の図でわかるように、このパターンには次のような参加者がいます。
- 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振る舞いパターンがどのように機能するかは、次の図を見ればわかります。
前の図でわかるように、このパターンには次のような参加者がいます。
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: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つの振る舞いデザインパターンです。 これは、操作内のアルゴリズムの基本コンポーネントを認識し、特定のステップをサブクラスに委譲するために使用できます。サブクラスはアルゴリズム全体の構造を変えることなく、これらのステップを再定義することができます。
よって、このパターンは次のときに使用できると言うことができます。
- アルゴリズムが存在し、このアルゴリズムの全体的な構造に影響を与えることなく、そこからいくつかのステップだけを変更する必要がある
- アルゴリズムの不変的な側面は一度だけ実装し、変数の動作の実装責任はサブクラスに委譲する必要がある
- サブクラスに共通する動作は、共通のクラスで考慮され、ローカライズされるべきである
- サブクラスの拡張をコントロールする必要がある
パターンの役割
このパターンがどのように機能するかを理解するには、パターンを表現した以下のチャートの助けを借りることができます。
前の図でわかるように、このパターンには次のような参加者がいます。
- 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:オブジェクト構造内の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
本連載の他の記事は、以下のリンクからご覧になれます。
- ソフトウェア開発とMQL5におけるデザインパターン(第1回):生成パターン
- ソフトウェア開発とMQL5におけるデザインパターン(第2回):構造パターン
- ソフトウェア開発とMQL5におけるデザインパターン(第3回):振る舞いパターン1
デザインパターンというトピックを理解することの重要性はいくら強調してもしすぎることはありません。なぜなら、これまで述べてきたように、デザインパターンはあらゆるソフトウェアの作成に非常に役立つためです。そのため、このトピックについてさらに読むことと、同じトピックに関する以下の参考文献を読むことをお勧めします。
- 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




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