LoginSignup
1
0

More than 1 year has passed since last update.

オブジェクト指向プログラミングPart4 ポリモーフィズム MQL5

Last updated at Posted at 2021-08-19

← オブジェクト指向プログラミングPart1 カプセル化

← オブジェクト指向プログラミングPart3 継承 (前回)

ポリモーフィズムとは

 ポリモーフィズムは「多態性」「多相性」などと訳される概念のことです。イメージとしては「ひとつの関数にたくさんの役割を与えて、その関数を呼び出す際に適したコードを実行させる」といったようなものだと思います。
 カプセル化、継承、ポリモーフィズムはそれぞれオブジェクト指向の3大要素と呼ばれる大事な概念です。

なぜポリモーフィズムは必要か

image.png

 ポリモーフィズムを落とし込んだプログラムはコード内で登場する関数が少なくなり、シンプルでわかりやすい記述ができます。「関数が呼ばれ方に応じて適した動作をする」ように設計にするので、ひとつひとつの関数が便利で使いやすいものと感じるようになるのではないかと思います。

ポリモーフィズムを実現させるには

 同じ名前の関数を、基底クラスと派生クラス両方に作ることをオーバーライドといい、同じ名前の関数で引数を変えたものを複数用意することをオーバーロードといいます。MQL5においてはオーバーライドとオーバーロードの両方を使ってポリモーフィズムを実現させるようです。
image.png

(MQL5公式ページより引用)
ポリモーフィズムの定義には、継承時の関数の再定義に加えて、クラス内での 1 つの関数のパラメータで異なる複数の実装が含まれます。これはクラス内に同名で型及び/またパラメータの異なる関数がいくつか存在出来ることを意味します。この場合、ポリモーフィズムは関数オーバーロードを通して実装されます。
https://www.mql5.com/ja/docs/basis/oop/polymorphism

オーバーロードの書き方、使い方

image.png
 同じ名前の関数で引数を変えたものを複数用意することをオーバーロードといいます。関数を呼び出したときの引数の違いによって、どの関数を呼ぶかを選択することができます。

// 基底クラス
class CRen{
public: 
   virtual void message() { Print("基底クラスです"); };
};

// 派生クラス
class CPractice :public CRen{
public:
   void message()         { Print("派生クラスです"); };
   void message(int x)    { printf("入力された数値は%dです",x); };
};

int OnInit()
  {

   CPractice practice;     // 派生クラスオブジェクト生成


   int X = 12345;

   practice.message();     // 引数なし(void)で呼ぶ

   practice.message(X);    // int型の引数で呼ぶ


   return(INIT_SUCCEEDED);
  }

動作させてみると↓
image.png
 同名の関数も引数を変えることで、どちらの関数にもアクセスすることができることがわかります。

オーバーライドの書き方、使い方

image.png
 基底クラスと派生クラスに同名の関数を宣言、定義します。virtualという修飾子は仮想関数といって、書かなくてもエラーは出ませんが、あとから両方にアクセスする仕組みを作るために便利なので基底クラス側の関数に書き足しておいてください。

// 基底クラス
class CRen{
public: 
   virtual void message() { Print("基底クラスです"); };
};

// 派生クラス
class CPractice :public CRen{
public:
   void message()         { Print("派生クラスです"); };
};

int OnInit()
  {
   CPractice practice;     // 派生クラスオブジェクト生成

   practice.message();


   return(INIT_SUCCEEDED);
  }

 まずはOnInit関数でいつも通りにmessage()関数を読んでみます。
image.png
 派生クラスのmessage()関数のみが呼ばれました。これが通常時の動作です。

では次に基底クラス側のmessage()関数と派生クラス側のmessage()関数を両方呼んでみます。

// 基底クラス
class CRen{
public: 
   virtual void message() { Print("基底クラスです"); };
};

// 派生クラス
class CPractice :public CRen{
public:
   void message()         { Print("派生クラスです"); };
};

int OnInit()
  {
   CRen *renPtr;           // 基底クラスポインタ生成

   CRen ren;               // 基底クラスオブジェクト生成
   CPractice practice;     // 派生クラスオブジェクト生成


   renPtr = &ren;          // 基底クラスポインタと基底クラスオブジェクトを使って呼ぶ
   renPtr.message();

   renPtr = &practice;     // 基底クラスポインタと派生クラスオブジェクトを使って呼ぶ
   renPtr.message();

   return(INIT_SUCCEEDED);
  }

 今度はポインタを使って
1. 基底クラスポインタと基底クラスオブジェクト(アドレス)を使って呼ぶ
2. 基底クラスポインタと派生クラスオブジェクト(アドレス)を使って呼ぶ
の2つのパターンを試してみます。

image.png
 同名で定義したmessage()関数ですが、基底クラスのmessage()関数と派生クラスのmessage()関数のどちらの関数も呼べるということがわかると思います。

スマートなコードを書いてみよう

 基底クラスポインタ + 呼び出したいクラスのオブジェクト(アドレス)

という形でどの関数にもアクセスできることが分かったところで、これを利用してスマートなコードを書いてみようと思います。

基底クラスと3つの派生クラスで合計4つのmessage()関数がある状況でやってみましょう。

// 基底クラス
class CKitei{
public: 
   virtual void message() { Print("基底クラスです"); };
}Kitei;

// 派生クラス1
class CHasei1 :public CKitei{
public:
   void message()         { Print("派生クラス1です"); };
}Hasei1;

// 派生クラス2
class CHasei2 :public CKitei{
public:
   void message()         { Print("派生クラス2です"); };
}Hasei2;

// 派生クラス3
class CHasei3 :public CKitei{
public:
   void message()         { Print("派生クラス3です"); };
}Hasei3;

 ここに定義した4つのmessage()関数を呼び出すコードをOnInit関数に書きます。

int OnInit()
  {
   // オブジェクトのアドレスを格納する配列を用意
   CKitei* p[4] = { &Kitei, &Hasei1, &Hasei2, &Hasei3 };

   // 全てのmessage()関数を呼び出す
   for(int i=0;i<4;i++)
      p[i].message();

   return(INIT_SUCCEEDED);
  }

image.png

 message()関数を呼び出す側のOnInit関数では短いコードで書くことができました。このように基底クラスポインタを配列にして管理することで、どのmessage()関数も自在に実行することができます。


続きを読むときはこちら
オブジェクト指向プログラミングPart5 抽象クラス →

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0