pekonoriブログ関連

Expert Advisor Programming for MetaTrader5【日本語訳 第六章】オブジェクト指向プログラミング

  1. HOME >
  2. pekonoriブログ関連 >

Expert Advisor Programming for MetaTrader5【日本語訳 第六章】オブジェクト指向プログラミング

オブジェクト指向プログラミング P57

MQL5の最もエキサイティングな新機能の1つは、オブジェクト指向プログラミングの追加です。オブジェクト指向プログラミング(略してOOP)はコードの再利用を促進し、不要な実装の詳細をユーザーから隠します。これにより、はるかに柔軟でコンパクトなスタイルのプログラミングが可能になります。

オブジェクト指向プログラミングの概念は本質的に抽象的であり、しばしば技術用語によって混乱します。これらは、新しいプログラマーが把握するのが難しい場合があります。しかし、OOPの基礎を学べば、それらが信じられないほど便利であることに気づくでしょう。

オブジェクト指向プログラミングは、クラスとオブジェクトの概念に基づいています。クラスは、一連の関連タスクを実行する変数と関数のコレクションです。クラス内に含まれる変数と関数はクラスのメンバーと呼ばれます。

クラスは、オブジェクトの設計図のようなものです。例えば車を考えてみましょう。車にはハンドル、ギアシフター、方向指示器、ヘッドライトなどがあります。車オブジェクトのクラスには、車の状態(速度、ギア、ヘッドライトのオン/オフ)を記述するすべての変数と、特定のタスク(加速または減速、ギアチェンジ、ヘッドライトの点灯・消灯など)を実行するすべての関数が含まれます。

クラスをテンプレートとしてオブジェクトが作成されます。クラスは車を記述し、オブジェクトは車そのものです。各オブジェクトには一意の名前が付けられています。これは、各車に一意の車両識別番号があるのと同様です。メーカーが、同じモデルの様々な車を多数製造できるように、必要な数のオブジェクトを作成できます。オブジェクトの変数は、高速道路で様々な車が様々な速度で走っているように、ほかのオブジェクトの変数とは異なります。

たとえば、EAでは、複数の指標を持つことができます。移動平均クロスの場合、少なくとも2つの移動平均インジケータがあります。各移動平均には異なる期間設定があり、異なる計算モードと価格設定がある場合があります。

移動平均インジケータは、クラスで表すことができます。移動平均指標クラスには、指標を作成し、プログラム実行中に現在の指標値を取得するために必要なすべての変数と関数が含まれています。このクラスを使用して、1つまたは複数のオブジェクトを作成します。各オブジェクトには独自の識別子、設定およびインジケータ値があります。

指標値を保持する動的系列配列の作成について心配する必要はありません。また、指標の初期化、指標ハンドルの保存、バッファから配列への指標値のコピーについて考える必要もありません。これらの詳細はすべて、クラスの実装で処理されます。オブジェクトを作成し、クラス関数を使用してこれらのタスクを実行するだけです。

class P58

クラスは関数と同様にグローバルスコープで宣言されます。クラスは、プログラム内またはインクルードファイル内に配置できます。クラス宣言では、classキーワードの後に一意の識別子が続きます。クラスのメンバーは角括弧内に配置され、access keywordsでソートされます。クラス宣言の閉じ括弧はセミコロンで終了します。

インジケータオブジェクトのクラス宣言の例を次に示します。

                  class CIndicator

{

    protected:

                  int handle;

                  double main[];

    public:

                  double Main(int pShift=0);

                  void Release();

                  CIndicator();

};

クラスの名前はCIndicatorです。3つのpublicメンバーと2つの保護されたメンバーがあります。クラス宣言内のすべての関数と変数の宣言がセミコロンで終了していることに注意してください。クラス宣言自体の閉じ括弧もセミコロンで終了します。CIndicatorクラスのパブリックメンバーにはMain()関数、Release()関数、およびクラスと同じ名前の規定のコントラクターが含まれます。保護されたメンバーにはハンドル変数とメインの[]配列が含まれます。

CIndicatorクラスについては第17章で詳しく説明しますので、まだその仕組みを理解していなくても心配する必要はありません。この章では、CIndicatorクラスを例として使用して、オブジェクト指向プログラミングの概念を説明します。

アクセス修飾子 P58

ラベルpublic、private、およびprotectedはアクセスキーワードです。これらは、変数または関数がクラス外で使用できるかどうかを決定します。アクセスキーワードの説明は次の通りです。

  • クラスのパブリックメンバーはクラスの外部で使用できます。これはプログラムがオブジェクトと対話する方法です。一般に、パブリックメンバーは重要なタスクを実行する関数です。パブリック関数はクラスのプライベート及び保護されたメンバーにアクセスして変更できます。
  • クラスのプライベートメンバーはクラス内の関数でのみ使用できます。プライベートメンバーは、クラス外ではアクセスできません。このクラスから派生したクラスはこれらのメンバーを継承しません。(継承については後程説明します。)プライベートメンバーは通常、クラスのパブリックメンバーによってアクセスされる内部関数および変数です。
  • クラスの保護されたメンバーは、基本的に派生クラスによって継承されるプライベートメンバーです。現在のクラスからクラスを派生させないことが確実でない限り、protectedキーワードを使用してください。

プログラムの残りの部分からクラスメンバーを隠すというこの概念は、カプセル化と呼ばれるOOPの概念です。クラスメンバーを非表示にすることで、それらが不必要に使用または変更されないようにします。

CIndicatorクラスは、移動平均インジケータクラスなど、ほかのインジケータクラスの親クラスとして使用するためのものです。すべてのインジケータが使用する機能を実装するためにこのクラスを作成しました。例えば、すべてのインジケータはインジケータハンドルを格納する変数を必要とするため、handleという名前の整数変数を作成しました。インジケータ値を保持するために、少なくとも1つの動的配列も必要です。インジケータの最初の(場合によっては唯一の)バッファーはメインバッファーと呼ばれるため、main[]という名前のdouble配列を宣言しました。これらの変数は両方とも保護されています。つまり、クラスの外からアクセスすることはできず、クラスのパブリックメンバーのみがアクセスできます。

クラスのパブリックメンバーに対して、指標値を更新してメインの[]配列の値にアクセスするMain()という名前の関数とメモリから指標を開放するRelease()関数を作成しました。クラスのパブリック関数を詳しく見てみましょう。Main()関数から始めます。この関数は、ハンドル変数を使用して指標データをメインの[]配列にコピーして指標を識別し、指定されたバーの値を返します。Main()関数の関数宣言は次の通りです。

                  double CIndicator::Main(int pShift=0)

{

    CopyBuffer(handle, 0, 0, MAX_COUNT, main);

    double value = NormalizeDouble(main[pShift], _Digits);

    return(value);

}

この関数宣言は、CIndicatorクラス宣言の後、グローバルスコープに配置されます。関数識別子の直前のCIndicatorに注意してください。二重コロン演算子(::)はスコープ解決演算子です。関数が特定のスコープに属していることを識別します。この場合、スコープはCIndicatorクラスです。

また、保護されたハンドルとmain[]変数がCopyBuffer()関数のパラメータとして使用されていることにも注意してください。クラスの保護されたプライベートメンバーにアクセスできる唯一の方法は、パブリッククラス関数を使用することです。プログラムのほかの場所からこれらのメンバーはアクセスしようとすると、コンパイルエラーが発生します。

クラス関数を宣言する好ましい方法は、上記で使用した方法です。しかし、クラス宣言自体の中で関数をインラインで宣言することもできます。これは、1行または2行で構成される非常に短い関数の場合に便利です。Release()関数を使用した例を次に示します。

                  class CIndicator

{

    public:

                  void Release() { IndicatorRelease(handle); }

    //・・・

};

Release()関数は、クラス宣言内の1行で宣言されています。IndicatorRelease()関数の呼び出しで構成されます。関数の内容は括弧内に含まれています。閉じ括弧の後にセミコロンを付ける必要はありませんが、セミコロンを付けてもコンパイラは文句を言いません。

コンストラクター P60

クラスからオブジェクトを作成すると、コンストラクタと呼ばれる関数が自動的に実行されます。コンストラクターは、オブジェクト内の変数を初期化するために使用されます。コンストラクターが明示的に定義されていない場合、コンパイラーはデフォルトのコンストラクタを作成して変数を初期化します。このデフォルトコンストラクタは、プログラマには表示されません。

CIndicator関数ではCIndicator()とも呼ばれる独自のデフォルトコンストラクタを宣言しました。コンストラクタの名前は、クラス識別子の名前と一致する必要があります。型は常にvoidであるため、既定のコンストラクタの戻り値の型を指定する必要はありません。コンストラクタのアクセスレベルはpublicである必要があります。

以下は、CIndicatorクラス宣言内のCIndicatorコンストラクタ宣言です。

                  class CIndicator

{

    public:

                  CIndicator();

    //・・・

};

そして、これが明示的に定義されたクラスコンストラクタです。コンストラクタの唯一の目的はmain[]配列を系列配列として設定することです。シリーズ配列については、この本の後半で詳しく説明します。

                  CIndicator::CIndicator(void)

{

    ArraySetAsSeries(main, true);

}

デフォルトのコンストラクターが必要ない場合は、明示的に宣言する必要が無いことに注意してください。オブジェクトの作成時に自動的に実行したいアクションがある場合は、これを行うデフォルトのコンストラクターを作成します。

パラメトリックコンストラクタや初期化リストなど、コンストラクタで実行できる高度な操作があります。オブジェクトの破棄時に呼び出されるデストラクタもあります。この本ではこれらの機能を使用しないため、詳細については読者に委ねます。コンストラクタとデストラクタについては、MQL5リファレンスの言語の基本 > オブジェクト指向プログラミングで学習できます。

派生クラス P61

OOPの最も便利な機能の1つは、継承の概念です。OOPでは、別のクラスをテンプレートとして使用してクラスを作成できます。新しいクラスは、親クラスのすべての関数と変数を継承します(private accessキーワードを使用するものを除く)。その後、新しい関数と変数を追加して、そのクラスを拡張できます。

これはまさに、CIndicatorクラスで行うことです。CIndicatorクラスは、ほかのインジケータクラスの親クラスになることを意図していることに注意してください。特定のインジケータの実装の詳細は派生クラスで処理されますが、基本的な変数と関数は親クラスで既に定義されています。

移動平均インジケータの派生クラスの例を次に示します。このクラスには、移動平均インジケータを初期化するInit()関数が1つ含まれています。

                  class CiMA : public CIndicator

{

    public :

                  int Init(string pSymbol, ENUM_TIMEFRAMES pTimeframe, int pMAPeriod,

int pMAShift, ENUM_MA_METHOD pMAMethod, ENUM_APPLIED_PRICE pMAPrice);

};

派生クラスの名前はCiMAです。クラス宣言でコロン(:)の後にpublic Cindicatorが続くことに注意してください。これは、CiMAクラスがCIndicatorクラスから派生することを指定します。CIndicatorクラスのpublicおよびprotectedメンバーはすべてCiMAクラスの一部になりました。

publicキーワードは、基本クラスのすべてのpublicおよびprotectedメンバーが、このクラスから派生したクラスでpublicまたはprotectedのままであることを指定します。また、protectedまたはprivateキーワードを指定することもできます。これにより、親クラスのすべてのpublicおよびprotectedメンバーのアクセスレベルが、すべての派生クラスでprotectedまたはprivateに変更されます。ただし、これを頻繁に行う必要はありません。

Init()関数の関数宣言を次に示します。この関数は、移動平均インディケータ設定をiMA()関数に渡し、iMA()関数はインディケータハンドルを返します。CIndicatorクラスのハンドル変数の使用法に注意してください。

                  int CiMA::Init(string pSymbol, ENUM_TIMEFRAMES pTimeframe, int pMAPeriod,

int pMAShift, ENUM_MA_METHOD pMAMethod, ENUM_APPLIED_PREICE pMAPrice)

                  {

                      handle = iMA(pSymbol, pTimeframe, pMAPeriod, pMAShift, pMAMethod, pMAPrice);

                      return(handle);

}

仮想機能 P62

場合によっては、派生クラスで関数が動作する方法を変更する必要があります。または、親クラスで関数を定義したいかもしれませんが、派生クラスでの実装に注意してください。これは、仮想関数を使用して実現できます。

再び車の例を使用しましょう。車のクラスには、ギアを変更する機能があります。ただし、マニュアルトランスミッションを搭載したギアを変更するプロセスとは異なります。したがって、親クラスで仮想ギア変更関数を宣言し、派生クラスで実際の関数を記述します。

車のクラスのコードで次のようになります。

                  class Car

{

    public:

                  virtual int ShiftGears(int gear) { return(gear); }

};

クラスの名前はCarです。ShiftGears()という名前の仮想関数という1つの関数を宣言しました。ShiftGears()宣言に空の関数本体を追加し、単一のreturn演算子のみを含めました。ShiftGears()関数はvirtualキーワードで宣言されています。これは、関数が派生クラスで定義されることを意味します。

マニュアルトランスミッション車の派生クラスを作成しましょう。

                  class ManualCar : public Car

{

    public:

                  virtual int ShiftGears(int gear);

};

このクラスはManualCarという名前で、マニュアルトランスミッション車用です。ここでShiftGears()関数を定義します。この関数はCarクラスの関数と同じ型とパラメータで宣言されています。virtualキーワードはオプションであり、MunualCarから新しいクラスを派生させる予定がない場合は省略できます。関数の本体は別の場所で定義されており、マニュアルトランスミッションを使用してギアをシフトするためのロジックが含まれています。Carクラスから派生したほかのクラスは、同様の方法でShiftGears()を定義します。

派生クラスに親クラスの関数と同じ名前とパラメータを持つ関数がある場合、派生クラスの関数は親クラスの関数をオーバーライドします。派生クラスで関数を再定義するこのプロセスは、ポリモーフィズムとして知られるOOPの概念です。

CiMAクラスのInit()関数は、CIndicatorクラスで仮想関数として宣言できます。これにより、CIndicatorから派生したすべてのクラスがInit()クラスを実装する必要があります。

                  class CIndicator

{

    protected:

                  int handle;

    public:

                  virtual int Init() { return(handle) ; }

};

Init()関数は、クラスのパブリックメンバーとして宣言されています。virtualキーワードは関数の実装がすべての派生クラスで実行されることを指定します。関数の本体は同じ行で宣言されています。この例では、関数は単純にハンドル変数の値を返します。CIndicatorに基づいて派生クラスを作成する場合、Init()関数を新しいクラスに実装する必要があります。

オブジェクト P63

移動平均インジケータのクラスを作成したので、オブジェクトを作成しましょう。変数、列挙型、または構造体を作成するのと同じ方法でオブジェクトを作成します。クラス名が型として使用され、オブジェクトには一意の識別子が与えられます。

                  CiMA objMa;

これにより、クラスCiMAに基づいてobjMaという名前のオブジェクトが作成されます。オブジェクトが作成されると、そのオブジェクトのコンストラクターが自動的に実行されます。CiMAクラスはCIndicatorクラスから派生するため、CIndicatorクラスのコンストラクターはCiMAクラスで使用できるようになり、CiMAクラスに基づくオブジェクトの作成時に自動的に実行されます。したがって、CIndicator()コンストラクターが呼び出され、オブジェクトの作成時にmain[]配列シリーズとして配列として設定されます。

                  CIndicator::CIndicator(void)

{

    ArraySetAsSeries(main, true);

}

オブジェクトが宣言されると、ドット( . )演算子を使用してパブリックメンバーにアクセスできます。最初に行う必要があるのは、Init()関数でインジケータを初期化することです。

                  objMa.Init(_Symbol, 0, MAPeriod, 0, MAMethod, MAPrice);

Init()関数はCIndicatorクラスで仮想関数として宣言され、CiMAクラスで定義されたことを思い出してください。Init()関数は、指定された設定を使用して移動平均インジケータのインジケータハンドルを作成します。インジケータハンドルは、CIndicatorクラスで定義されている保護された変数ハンドルに格納されます。

次に、Main()関数を使用してメインの[]配列にインジケータ値を入力し、特定のバーのインジケータ値を取得します。メイン[]配列は保護されたクラスメンバーであるため、Main()関数などのパブリック関数を介してのみアクセスできます。このコード行は、現在のバーの移動平均値をログに出力します。

                  Print(objMa.Main());

プログラムに必要な数のオブジェクトを作成できます。2番目の移動平均インジケータが必要な場合は、別の一意の識別子を使用して宣言し、上記のようにオブジェクトのパブリックメンバーにアクセスします。一般的なタスクを実行するクラスを作成することで、時間を節約し、エラーを減らし、プログラムのコード量を減らすことができます。EAに関する本の残りの部分では、一般的な取引タスクを実行するためのクラスの作成に焦点を当てます。

≪≪【日本語訳 第五章】関数

【日本語訳 第七章】MQL5プログラムの構造≫≫

-pekonoriブログ関連