ソフトウェア開発とMQL5におけるデザインパターン(第3回):振る舞いパターン1
はじめに
この記事では、ソフトウェア領域におけるデザインパターンについての連載を続けます。本連載の前の2つの記事では生成パターンと構造パターンの2タイプを特定しました。この記事では、振る舞いパターンとは何か、それがソフトウェアの作成、構築、開発時にどのように役立つかを特定して理解した後、3タイプ目である振る舞いデザインパターンについて説明します。 その後、MQL5でそれらをどのように使用し、信頼性が高く、保守、再利用、拡張が可能な、十分にテストされたMetaTrader 5用のソフトウェアを作成できるかを学びます。
以下のトピックは、この重要なパターンをカバーするために言及する内容についてです。
本連載から読む記事がこれが初めての方は、ソフトウェア開発における最も重要なトピックの1つであるデザインパターンを全体的に捉えるために、生成パターンと構造パターンに関する他の記事もご覧ください。
振る舞いパターン
デザインパターンについて話す文脈で、生成パターンと構造パターンについて話した後、これらのデザインパターンの最後のタイプである振る舞いパターンについて話します。生成パターンとは、オブジェクトを創造し、構成し、表現することによって、独立したソフトウェアやシステムを創造するのに役立つパターンであることを学びました。それに加えて、構造パターンとは、作成されたオブジェクトやクラスを使用して、より大きな構造を構築するために使用することができるパターンであることを学びました。
この記事では、オブジェクト間で責任をどのように割り当てたり設定したりするかに関係する振る舞いパターンを提示します。また、これらは、オブジェクト同士がどのように通信や相互作用をおこなうかを識別することもできます。
- Chain of responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
1つの記事ではカバーしきれないほど多くのパターンがあるため、この記事では最初の5つのパターンに焦点を当てます。
- Chain of responsibility:複数のオブジェクトが送信者からの要求を処理する機会を与えることで、送信者とその受信者を切り離せるようにする。また、受信オブジェクトをチェーン化し、このチェーンに沿って要求を渡すことで、オブジェクトからの処理を完了させる
- Command:要求をオブジェクトとしてカプセル化した後、異なる要求を持つクライアントにパラメータを設定したり、要求をキューに入れたり、ログに記録したり、取り消し可能な操作をサポートしたりする権限を与える
- Interpreter:与えられた言語の文法表現を定義し、この定義された表現を使って、その言語の文として必要なものを解釈できるインタープリタを提供する
- Iterator:集成体オブジェクトの要素に、その基礎となる表現を公開することなくシーケンスでアクセスする必要がある場合、このパターンはそのためのメソッドを提供する
- Mediator:カプセル化されたオブジェクトを通して、一連のオブジェクトがどのように相互作用するかを定義し、オブジェクトの相互作用を独立に変化させることで、分離を促進する
デザインパターンに関する前の2つの記事を読んだ方には、すべてのパターンをカバーするために使用されるアプローチにはお馴染みかと思います。
- パターンの役割
- デザインパターンが解決する問題
- MQL5での使用方法
Chain of responsibility
このセクションでは、Chain of responsibilityとは何かを理解するために、Chain of responsibilityができること、解決できること、MQL5でどのように使用できるかを学びます。クライアントからの要求を処理する必要があるとき、全員の責任に基づいてクライアントの要求を処理できるオブジェクトが多数ある場合、このパターンを使って処理することができます。
このパターンを使用することの利点とは裏腹に、以下のような落とし穴もあります。
- チェーンが長い場合の効率の問題
- 要求には受信者が指定されていないので、この要求は処理されずにチェーン内のオブジェクトに渡される可能性があり、要求処理の保証がない。それに加えて、適切に設定されたチェーンがなければ、要求を処理できないことを知る必要がある
このパターンは、多くのオブジェクトが要求を処理する機会を与えることで、要求の送信者と受信者を切り離すのに便利です。これは、受信オブジェクトを連結し、全員に要求を渡して、どのオブジェクトが要求を処理できるかを確認することで行われます。
以下はパターンの構造を示す図です。
前の図でわかるように、次のような参加者がいます。
- Client:チェーンのオブジェクトが処理する要求を開始
- Handler:要求を処理するインターフェイスを定義し、後継者リンクを実装することもできる
- ConcreteHandler:要求をその責任に基づいて処理するオブジェクトで、要求を処理できないときに渡すことができる後継者へのアクセス権を持つ
デザインパターンが解決する問題
このパターンは以下に該当する場合に使用できます。
- 要求を処理できるオブジェクトがたくさんある
- 送信者と受信者を切り離す必要がある
- 受信者に言及することなく、多くのオブジェクトのいずれかに要求を出す必要がある
- 要求を処理できるオブジェクトのセットを動的に指定している
つまり、このパターンは以下のことを解決できます。
- カップリングの低減(送信側と受信側を切り離し、独立した変更を適用できるようにする)
- オブジェクトに責任を割り当てたり、分配したりする際に柔軟性を与える
MQL5での使用方法
このセクションでは、MQL5でこのパターンを使用して効果的なMetaTrader 5ソフトウェアを作成する方法を学びます。MQL5でChain of responsibilityをコード化する手順は次のとおりです。
Chain_Of_Responsibility領域を宣言し、namespaceキーワードを使用して、その中にパターンの関数と変数を含めます。
namespace Chain_Of_Responsibility
クライアントからの要求を処理するHandlerクラスの参加者を宣言します。
class Handler { public: Handler* successor; virtual void HandleRequest(int)=0; ~Handler(void); }; Handler::~Handler(void) { delete successor; }
担当する要求を処理するまたは要求を処理できる場合はその後継者に要求を渡す、ConcreteHandler1クラスの参加者を宣言します。
class ConcreteHandler1:public Handler { public: void HandleRequest(int); }; void ConcreteHandler1::HandleRequest(int request) { if(request==1) Print("The request: ",request,". The request handled by: ",&this); else if(CheckPointer(successor)) { Print("The request: ",request,". The request cannot be handled by ",&this,", but it is forwarding to the successor..."); successor.HandleRequest(request); } }
ConcreteHandler2クラスも参加者として宣言します。
class ConcreteHandler2:public Handler { public: void HandleRequest(int); }; void ConcreteHandler2::HandleRequest(int request) { if(request==2) Print("The request: ",request,". The request handled by: ",&this); else if(CheckPointer(successor)) { Print("The request: ",request,". The request cannot be handled by ",&this,", forwarding to successor..."); successor.HandleRequest(request); } }
具体的なハンドラへの要求を開始するクライアントクラスをチェーンで宣言します。
class Client { public: string Output(); void Run(); }; string Client::Output() { return __FUNCTION__; }
クライアントを実行する関数は、要求をチェーンに送り、要求は処理されるか後継者に渡されます。
void Client::Run() { Handler* h1=new ConcreteHandler1(); Handler* h2=new ConcreteHandler2(); h1.successor=h2; h1.HandleRequest(1); h1.HandleRequest(2); delete h1; }
MQL5でChain of responsibilityパターンを使用するための完全なコードは次のように1つのブロックになります。
//+------------------------------------------------------------------+ //| Chain_Of_Responsibility.mqh | //+------------------------------------------------------------------+ namespace Chain_Of_Responsibility { class Handler { public: Handler* successor; virtual void HandleRequest(int)=0; ~Handler(void); }; Handler::~Handler(void) { delete successor; } class ConcreteHandler1:public Handler { public: void HandleRequest(int); }; void ConcreteHandler1::HandleRequest(int request) { if(request==1) Print("The request: ",request,". The request handled by: ",&this); else if(CheckPointer(successor)) { Print("The request: ",request,". The request cannot be handled by ",&this,", but it is forwarding to the successor..."); successor.HandleRequest(request); } } class ConcreteHandler2:public Handler { public: void HandleRequest(int); }; void ConcreteHandler2::HandleRequest(int request) { if(request==2) Print("The request: ",request,". The request handled by: ",&this); else if(CheckPointer(successor)) { Print("The request: ",request,". The request cannot be handled by ",&this,", but it is forwarding to successor..."); successor.HandleRequest(request); } } class Client { public: string Output(); void Run(); }; string Client::Output() { return __FUNCTION__; } void Client::Run() { Handler* h1=new ConcreteHandler1(); Handler* h2=new ConcreteHandler2(); h1.successor=h2; h1.HandleRequest(1); h1.HandleRequest(2); delete h1; } }
Command
このセクションでは、もう1つの振る舞いパターンであるCommandパターンについて説明します。このパターンは「Action and Transaction」(行動とトランザクション)としても知られています。このパターンによって、要求をオブジェクトにカプセル化することができます。これにより、送信者や受信者を変更せずに、さまざまな要求のパラメータを設定できるようになります。つまり、送信者、プロセッサー、受信者が分離されます。これは、クラス内に巨大な機能がある場合に非常に役立ちます。このパターンは取り消し操作にも対応しています。
このパターンを使用する際には、ほとんどのものと同じようにいくつかの落とし穴があります。
- 通常は他のパターンと組み合わせて使用される
- さまざまなコマンドや要求を処理するために、多くのクラスやオブジェクトが必要になる
- コマンドごとにオブジェクトを作成するのは、オブジェクト指向デザインに反する
簡単に言うと、コマンドを受信して受信者に送信するカプセル化されたインボーカーを作成します。
以下はCommandパターンの図です。
前の図でわかるように、このパターンには次のような参加者がいます。
- Command:操作を実行するインターフェイスを宣言できる
- ConcreteCommand:受信者と行動やコマンドの間のリンクを作成し、さらに受信者上で対応する操作を呼び出すことで、実行を実装する
- Client:ConcreteCommandの作成と受信者の設定の2つをおこなう
- Invoker:要求を実行するCommandを受信する
- Receiver:要求の実行に関連する操作のメソッドを特定し、任意のクラスを指定できる
デザインパターンが解決する問題
- このパターンは、次のような場合に使用できます。
- 実行する行動ごとにオブジェクトにパラメータを設定する必要がある
- 指定、キューイング、実行をそれぞれのタイミングでおこなうような操作が必要である
- やり直し操作をサポートするために使えるものが必要である
- システムがクラッシュした場合、その場合に再適用される変更のロギングをサポートする必要がある
- システムの構造として、原始的な操作の上に構築された高レベルの操作が必要である
MQL5での使用方法
このセクションでは、MQL5でCommandパターンを使用するための方法を紹介します。
namespaceキーワードを使って、関数、変数、クラス...などを指定するCommand空間を宣言します。
namespace Command
要求の操作を実行するメソッドを特定する参加者として、Receiverクラスを宣言します。
class Receiver { public: Receiver(void); Receiver(Receiver&); void Action(void); }; Receiver::Receiver(void) { } Receiver::Receiver(Receiver &src) { } void Receiver::Action(void) { }
操作インターフェイスを宣言する参加者として、Commandクラスを宣言します。
class Command { protected: Receiver* m_receiver; public: Command(Receiver*); ~Command(void); virtual void Execute(void)=0; }; Command::Command(Receiver* receiver) { m_receiver=new Receiver(receiver); } Command::~Command(void) { if(CheckPointer(m_receiver)==1) { delete m_receiver; } }
ConcreteCommandクラスを参加者として宣言し、受信者と行動またはコマンドのリンクを作成し、受信者の操作を呼び出した後にexecute()を実装します。
class ConcreteCommand:public Command { protected: int m_state; public: ConcreteCommand(Receiver*); void Execute(void); }; ConcreteCommand::ConcreteCommand(Receiver* receiver): Command(receiver), m_state(0) { } void ConcreteCommand::Execute(void) { m_receiver.Action(); m_state=1; }
要求を実行するためのCommandを受け取る参加者として、Invokerクラスを宣言します。
class Invoker { public: ~Invoker(void); void StoreCommand(Command*); void Execute(void); protected: Command* m_command; }; Invoker::~Invoker(void) { if(CheckPointer(m_command)==1) { delete m_command; } } void Invoker::StoreCommand(Command* command) { m_command=command; } void Invoker::Execute(void) { m_command.Execute(); }
クライアントクラスを参加者として宣言し、具体的なCommandの受信者を作成して実行します。
class Client { public: string Output(void); void Run(void); }; string Client::Output(void) { return __FUNCTION__; } void Client::Run(void) { Receiver receiver; Invoker invoker; invoker.StoreCommand(new ConcreteCommand(&receiver)); invoker.Execute(); }
完全なコードは次のように1つのブロックになります。
//+------------------------------------------------------------------+ //| Command.mqh | //+------------------------------------------------------------------+ namespace Command { class Receiver { public: Receiver(void); Receiver(Receiver&); void Action(void); }; Receiver::Receiver(void) { } Receiver::Receiver(Receiver &src) { } void Receiver::Action(void) { } class Command { protected: Receiver* m_receiver; public: Command(Receiver*); ~Command(void); virtual void Execute(void)=0; }; Command::Command(Receiver* receiver) { m_receiver=new Receiver(receiver); } Command::~Command(void) { if(CheckPointer(m_receiver)==1) { delete m_receiver; } } class ConcreteCommand:public Command { protected: int m_state; public: ConcreteCommand(Receiver*); void Execute(void); }; ConcreteCommand::ConcreteCommand(Receiver* receiver): Command(receiver), m_state(0) { } void ConcreteCommand::Execute(void) { m_receiver.Action(); m_state=1; } class Invoker { public: ~Invoker(void); void StoreCommand(Command*); void Execute(void); protected: Command* m_command; }; Invoker::~Invoker(void) { if(CheckPointer(m_command)==1) { delete m_command; } } void Invoker::StoreCommand(Command* command) { m_command=command; } void Invoker::Execute(void) { m_command.Execute(); } class Client { public: string Output(void); void Run(void); }; string Client::Output(void) { return __FUNCTION__; } void Client::Run(void) { Receiver receiver; Invoker invoker; invoker.StoreCommand(new ConcreteCommand(&receiver)); invoker.Execute(); } }
Interpreter
もう1つの振る舞いパターンは、特定の言語を通じてオブジェクト間の相互作用を設定し、後でこの言語の内容を説明および解釈するためにこの表現を使用できるインタプリタでルールまたは文法の表現を定義できるようにします。
この種類のパターンを使う場合、次のような落とし穴があります。
- ルールや文法が複雑で、その程度が高い場合、維持するのが難しい
- 特定の状況で使用できる
このパターンは、ある言語の文法を定義し、その言語の内容を表現し、その内容の解釈を得る方法を説明するのに役立ちます。
Interpreterパターンの図は以下のようになります。
前の図でわかるように、このパターンには次のような参加者がいます。
- AbstractExpression:抽象的な解釈(コンテキスト)の操作を宣言できる
- TerminalExpression:文法中の終端記号に関連する解釈の操作を実装することができ、コンテンツ内の各終端記号の要件としてインスタンスを作成する
- NonterminalExression:文法内のすべてのルールに対して、1つのクラスが必要であり、そのクラスはルールごとにAbstractExpressionのインスタンス変数を保持する。文法内の非終端記号の解釈の操作を実装する
- Context:Interpreterのグローバル情報が含まれている
- Client:文法によって定義される必要がある内容を言語で表現するための抽象構文木を構築し、Interpreterの操作を呼び出す
デザインパターンが解決する問題
確認したように、このパターンは、解釈する必要のある言語があり、その言語で内容を定義したり表現したりできる場合に使用できます。
このパターンを使える最良のケースを以下に挙げます。
- 言語の文法が単純である(文法が複雑だとクラスの階層が大きくなり、管理不能な状態につながる可能性があるため)
- インタープリタの効率性がそれほど重要でない
つまり、このパターンを使用することで、次のようなメリットが得られます。
- 継承を使った文法の更新や拡張を簡単かつスムーズにおこなうことができる
- 文法の実装も簡単になる
- 表現の新しい解釈方法を追加するのが簡単になる
MQL5での使用方法
このセクションでは、このタイプのパターンをコード化または使用する簡単な方法を紹介します。InterpreterをMQL5で使用する手順を以下に示します。
関数、変数、クラスを定義し、宣言するために使用するInterpreterの領域を、お馴染みのようにnamespaceキーワードを使用して宣言します。
namespace Interpreter
コンテキストクラスを参加者として宣言します。
class Context { public: string m_source; char m_vocabulary; int m_position; bool m_result; //--- Context(char,string); void Result(void); }; Context::Context(char vocabulary,string source): m_source(source), m_vocabulary(vocabulary), m_position(0), m_result(false) { } void Context::Result(void) { }
Abstractクラスを参加者として宣言します。
class AbstractExpression { public: virtual void Interpret(Context&)=0; };
interpretメソッドを実装する参加者としてTerminalExpressionクラスを宣言します。
class TerminalExpression:public AbstractExpression { public: void Interpret(Context&); }; void TerminalExpression::Interpret(Context& context) { context.m_result= StringSubstr(context.m_source,context.m_position,1)== CharToString(context.m_vocabulary); }
NonterminalExpressionクラスを参加者として宣言します。
class NonterminalExpression:public AbstractExpression { protected: AbstractExpression* m_nonterminal_expression; AbstractExpression* m_terminal_expression; public: void Interpret(Context&); ~NonterminalExpression(void); }; NonterminalExpression::~NonterminalExpression(void) { delete m_nonterminal_expression; delete m_terminal_expression; } void NonterminalExpression::Interpret(Context& context) { if(context.m_position<StringLen(context.m_source)) { m_terminal_expression=new TerminalExpression; m_terminal_expression.Interpret(context); context.m_position++; if(context.m_result) { m_nonterminal_expression=new NonterminalExpression; m_nonterminal_expression.Interpret(context); } } }
クライアントを参加者として宣言します。
class Client { public: string Output(void); void Run(void); }; string Client::Output(void) {return __FUNCTION__;} void Client::Run(void) { Context context_1('a',"aaa"); Context context_2('a',"aba"); AbstractExpression* expression; expression=new NonterminalExpression; expression.Interpret(context_1); context_1.Result(); delete expression; expression=new NonterminalExpression; expression.Interpret(context_2); context_2.Result(); delete expression; }
Interpreterパターンの完全なコードは次のように1つのブロックになります。
//+------------------------------------------------------------------+ //| Interpreter.mqh | //+------------------------------------------------------------------+ namespace Interpreter { class Context { public: string m_source; char m_vocabulary; int m_position; bool m_result; Context(char,string); void Result(void); }; Context::Context(char vocabulary,string source): m_source(source), m_vocabulary(vocabulary), m_position(0), m_result(false) { } void Context::Result(void) { } class AbstractExpression { public: virtual void Interpret(Context&)=0; }; class TerminalExpression:public AbstractExpression { public: void Interpret(Context&); }; void TerminalExpression::Interpret(Context& context) { context.m_result= StringSubstr(context.m_source,context.m_position,1)== CharToString(context.m_vocabulary); } class NonterminalExpression:public AbstractExpression { protected: AbstractExpression* m_nonterminal_expression; AbstractExpression* m_terminal_expression; public: void Interpret(Context&); ~NonterminalExpression(void); }; NonterminalExpression::~NonterminalExpression(void) { delete m_nonterminal_expression; delete m_terminal_expression; } void NonterminalExpression::Interpret(Context& context) { if(context.m_position<StringLen(context.m_source)) { m_terminal_expression=new TerminalExpression; m_terminal_expression.Interpret(context); context.m_position++; if(context.m_result) { m_nonterminal_expression=new NonterminalExpression; m_nonterminal_expression.Interpret(context); } } } class Client { public: string Output(void); void Run(void); }; string Client::Output(void) {return __FUNCTION__;} void Client::Run(void) { Context context_1('a',"aaa"); Context context_2('a',"aba"); AbstractExpression* expression; expression=new NonterminalExpression; expression.Interpret(context_1); context_1.Result(); delete expression; expression=new NonterminalExpression; expression.Interpret(context_2); context_2.Result(); delete expression; } }
Iterator
ここでは、オブジェクト間の相互作用や通信の方法を設定する振る舞いパターンの1つであるIteratorデザインパターンについて説明します。このパターンは、リストのような集成体オブジェクトの存在をサポートしたり、その根底にある表現の詳細や内部構造を公開することなく、シーケンス化された方法で要素にアクセスする方法を与えてくれたりします。Cursor(カーソル)とも呼ばれます。
Iteratorパターンを使用することの利点とは裏腹に、以下のような落とし穴もあります。
- コレクションがある場合、そのインデックスにアクセスすることはできない
- たとえば、前のものに誘導する必要がある場合に単方向状態になることがあるが、その解決策があるのは一部の言語
- 場合によっては、インデックスを作成してそれをループするほうがこのパターンを使用するよりも速い
このパターンは、異なる方法で走査される可能性のある複雑な集合体の場合のバリエーションをサポートすることができます。なぜなら、更新された走査をサポートするためにIteratorのサブクラスを定義することに加えて、Iteratorのインスタンスを置き換えることによって走査アルゴリズムを更新することが容易だからです。集合体のインターフェイスがシンプルになります。Iteratorの走査状態を追跡することで、一度に多くの走査を処理することができます。
このパターンの図は以下のようになります。
上記のIterator構造図でわかるように、このパターンには次のような参加者がいます。
- Iterator:要素へのアクセスや走査に使用できるインターフェイスを定義
- ConcreteIterator:Iteratorのインターフェイスを実装できるようにし、集成体走査を追跡する
- Aggregate:オブジェクトIteratorを作成するために使用できるインターフェイスを特定できるようにする
- ConcreteAggregate:適切なConcreteIteratorのインスタンスを返り値として取得するために、Iteratorインターフェイスを実装できるようにできる
デザインパターンが解決する問題
Iteratorパターンは、以下のような場合に使用できます。
- 集成体オブジェクトのコンテンツにアクセスする必要があるが、その内部表現を公開する必要がない
- 集成体オブジェクトの走査を何度もおこなうのであれば、それをサポートする必要がある
- 異なる集成体構造を持つ場合、それらを走査するための統一されたインターフェイスを提示する必要がある
MQL5での使用方法
このセクションでは、このパターンをMQL5でどのように使うことができるかを、以下の手順を通して学んでいきます。
#defineプリプロセッサを使用して、ERRITERAOR-UT-OF-BOUNDSを定義します。
#define ERR_ITERATOR_OUT_OF_BOUNDS 1
templateキーワードを使用し、定義されたIteratorインターフェイスのCurrentItemとしてTを宣言します。
template<typename T> interface Iterator { void First(void); void Next(void); bool IsDone(void); T CurrentItem(void); };
また、templateキーワードを使い、定義されたAggregateインターフェイスの演算子としてTを宣言します。
template<typename T> interface Aggregate { Iterator<T>* CreateIterator(void); int Count(void); T operator[](int at); void operator+=(T item); };
Iteratorインターフェイスを実装し、ConcreteIteratorクラスを宣言した後、集成体の走査を現在位置として追跡します。
template<typename T> class ConcreteIterator:public Iterator<T> { public: void First(void); void Next(void); bool IsDone(void); T CurrentItem(void); ConcreteIterator(Aggregate<T>&); protected: Aggregate<T>* m_aggregate; int m_current; }; template<typename T> ConcreteIterator::ConcreteIterator(Aggregate<T>& aggregate): m_aggregate(&aggregate), m_current(0) { } template<typename T> void ConcreteIterator::First(void) { m_current=0; } template<typename T> void ConcreteIterator::Next(void) { m_current++; if(!IsDone()) { } } template<typename T> bool ConcreteIterator::IsDone(void) { return m_current>=m_aggregate.Count(); } template<typename T> string ConcreteIterator::CurrentItem(void) { if(IsDone()) { SetUserError(ERR_ITERATOR_OUT_OF_BOUNDS); return NULL; } return m_aggregate[m_current]; }
適切な具象Iteratorのインスタンスを返り値として得るためのIterator生成インターフェイスを実装します。
class ConcreteAggregate:public Aggregate<string> { public: Iterator<string>* CreateIterator(void); int Count(void); void operator+=(string item); string operator[](int at); protected: string m_items[]; }; Iterator<string>* ConcreteAggregate::CreateIterator(void) { return new ConcreteIterator<string>(this); } void ConcreteAggregate::operator+=(string item) { int size=ArraySize(m_items); ArrayResize(m_items,size+1); m_items[size]=item; } string ConcreteAggregate::operator[](int at) { return m_items[at]; } int ConcreteAggregate::Count() { return ArraySize(m_items); }
MQL5でIteratorパターンを使用するための完全なコードは次のように1つのブロックになります。
//+------------------------------------------------------------------+ //| 201021_104101.mqh | //+------------------------------------------------------------------+ #define ERR_ITERATOR_OUT_OF_BOUNDS 1 template<typename T> interface Iterator { void First(void); void Next(void); bool IsDone(void); T CurrentItem(void); }; template<typename T> interface Aggregate { Iterator<T>* CreateIterator(void); int Count(void); T operator[](int at); void operator+=(T item); }; template<typename T> class ConcreteIterator:public Iterator<T> { public: void First(void); void Next(void); bool IsDone(void); T CurrentItem(void); ConcreteIterator(Aggregate<T>&); protected: Aggregate<T>* m_aggregate; int m_current; }; template<typename T> ConcreteIterator::ConcreteIterator(Aggregate<T>& aggregate): m_aggregate(&aggregate), m_current(0) { } template<typename T> void ConcreteIterator::First(void) { m_current=0; } template<typename T> void ConcreteIterator::Next(void) { m_current++; if(!IsDone()) { } } template<typename T> bool ConcreteIterator::IsDone(void) { return m_current>=m_aggregate.Count(); } template<typename T> string ConcreteIterator::CurrentItem(void) { if(IsDone()) { SetUserError(ERR_ITERATOR_OUT_OF_BOUNDS); return NULL; } return m_aggregate[m_current]; } class ConcreteAggregate:public Aggregate<string> { public: Iterator<string>* CreateIterator(void); int Count(void); void operator+=(string item); string operator[](int at); protected: string m_items[]; }; Iterator<string>* ConcreteAggregate::CreateIterator(void) { return new ConcreteIterator<string>(this); } void ConcreteAggregate::operator+=(string item) { int size=ArraySize(m_items); ArrayResize(m_items,size+1); m_items[size]=item; } string ConcreteAggregate::operator[](int at) { return m_items[at]; } int ConcreteAggregate::Count() { return ArraySize(m_items); }
Mediator
もう1つの振る舞いデザインパターンは、オブジェクト同士がどのように相互作用するかを設定する際に使用できます。このパターンはMediatorパターンです。オブジェクトの集合があり、このオブジェクトの集合がどのように相互作用するかを記述するためにカプセル化されたオブジェクトを定義する必要があるときに使用することができます。また、分離も可能で、オブジェクトの相互作用を独立に変化させることができます。
どんなものにも利点と落とし穴があるのと同じように、Mediatorデザインパターンにも次のような落とし穴があります。
- 何に対してもMediatorが1つ作成される
- 他のパターンと併用される
Mediatorパターンの識別として述べたように、このパターンは、各オブジェクトを明示的に記述することなく、オブジェクト間の相互作用メソッドを設定するのに役立つと言えます。そのため、オブジェクト間を分離するのに役立ちます。また、ルーターとして使用されることもあり、通信管理にも使用されます。
Mediatorパターンの構造を見るための図を以下に示します。
前のパターン構造図でわかるように、このパターンには次のような参加者がいます。
- Mediator:Colleagueのオブジェクトと通信するためのインターフェイスを特定する
- ConcreteMediator:Colleagueのオブジェクトを調整することで、協力の振る舞いを実装する。そのColleagueは既知であり、ConcreteMediatorによって保守可能である
- Colleagueクラス:オブジェクトのMediatorは、Colleagueの各クラスによって知られている。それに加えて、各Colleagueは、この通信が必要なとき、あるいは他のColleagueと通信する予定があるとき、いつでもそのMediatorと通信することができる。
デザインパターンが解決する問題
これまで理解してきたことを通して、このパターンは次のような場合に使用できます。
- オブジェクトのセットは、その方法を明確に定義した後で相互に通信できるが、この通信の方法は複雑でである
- オブジェクトは他のオブジェクトと通信するため、再利用が難しくなる
- 多くのサブクラスを作ることなく、クラス間で分散している動作をカスタマイズする必要がある
よって、このパターンについて、次を言うことができます。
- サブクラスを制限できるようにする
- Colleagueの分離に役立つ
- オブジェクトプロトコルをシンプルにする
- オブジェクト間の協力は抽象化される
- コントロールが一元化される
MQL5での使用方法
下の手順でこのパターンをMQL5で使用します。
interfaceキーワードを使用してColleagueインターフェイスを作成します。
interface Colleague { void Send(string message); };interfaceキーワードを使ってMediatorインターフェイスを作成します。
interface Mediator { void Send(string message,Colleague& colleague); };
ConcreteColleague1クラスを宣言します。
class ConcreteColleague1:public Colleague { protected: Mediator* m_mediator; public: ConcreteColleague1(Mediator& mediator); void Notify(string message); void Send(string message); }; ConcreteColleague1::ConcreteColleague1(Mediator& meditor): m_mediator(&meditor) { } void ConcreteColleague1::Notify(string message) { } void ConcreteColleague1::Send(string message) { m_mediator.Send(message,this); }
ConcreteColleague2クラスを宣言します。
class ConcreteColleague2:public Colleague { protected: Mediator* m_mediator; public: ConcreteColleague2(Mediator& mediator); void Notify(string message); void Send(string message); }; ConcreteColleague2::ConcreteColleague2(Mediator& mediator): m_mediator(&mediator) { } void ConcreteColleague2::Notify(string message) { } void ConcreteColleague2::Send(string message) { m_mediator.Send(message,this); }
ConcreteMediatorクラスを宣言します。
class ConcreteMediator:public Mediator { public: ConcreteColleague1* colleague_1; ConcreteColleague2* colleague_2; void Send(string message,Colleague& colleague); }; void ConcreteMediator::Send(string message,Colleague& colleague) { if(colleague_1==&colleague) colleague_2.Notify(message); else colleague_1.Notify(message); }
MQL5でMediatorデザインパターンを使用するための完全なコードは次のように1つのブロックになります。
//+------------------------------------------------------------------+ //| Mediator.mqh | //+------------------------------------------------------------------+ interface Colleague { void Send(string message); }; interface Mediator { void Send(string message,Colleague& colleague); }; class ConcreteColleague1:public Colleague { protected: Mediator* m_mediator; public: ConcreteColleague1(Mediator& mediator); void Notify(string message); void Send(string message); }; ConcreteColleague1::ConcreteColleague1(Mediator& meditor): m_mediator(&meditor) { } void ConcreteColleague1::Notify(string message) { } void ConcreteColleague1::Send(string message) { m_mediator.Send(message,this); } class ConcreteColleague2:public Colleague { protected: Mediator* m_mediator; public: ConcreteColleague2(Mediator& mediator); void Notify(string message); void Send(string message); }; ConcreteColleague2::ConcreteColleague2(Mediator& mediator): m_mediator(&mediator) { } void ConcreteColleague2::Notify(string message) { } void ConcreteColleague2::Send(string message) { m_mediator.Send(message,this); } class ConcreteMediator:public Mediator { public: ConcreteColleague1* colleague_1; ConcreteColleague2* colleague_2; void Send(string message,Colleague& colleague); }; void ConcreteMediator::Send(string message,Colleague& colleague) { if(colleague_1==&colleague) colleague_2.Notify(message); else colleague_1.Notify(message); }
結論
プログラミングやソフトウェア開発において最も重要なトピックの1つであるデザインパターンの3つ目のタイプについて説明しました。この記事では、いくつかの振る舞いデザインパターンを紹介し、それがどのようなもので、どのように再利用、拡張、保守可能で、テストされたソフトウェアを作成するのに役立つのかについて説明しました。各パターンで何ができるのか、各パターンを使用することで解決できる問題や課題、各パターンの利点と落とし穴、MQL5で各パターンを使用してMetaTrader 5用の効果的な取引システムを作成する方法について学びました。
振る舞いデザインパターンの中から以下のパターンをカバーしました。
- Chain of responsibilities
- Command
- Interpreter
- Iterator
- Mediator
デザインパターンについての記事を読むのがこれが初めての方は、「ソフトウェア開発とMQL5におけるデザインパターン(第1回):生成パターン」と「ソフトウェア開発とMQL5におけるデザインパターン(第2回):構造パターン」をお読みになることをお勧めします。他のタイプのデザインパターンについてもっと学ぶ必要がある場合、ぜひご参考ください。
効果的なソフトウェアを作成するのに役立つので、デザインパターンについてさらにお読みになることをお勧めします。
- 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著)
最も人気のあるテクニカル指標を使用したMetaTrader 5用の取引システムの作成に関する記事をもっとお読みになりたい場合は、私の出版物ページから他の記事をご確認ください。取引に役立つ洞察を得たり、結果を強化したり、開発者としての素養を高め、お取り組み中のプロジェクトを改善したりするために、これらの記事がお役に立つかと思います。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/13796
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索