← オブジェクト指向プログラミングPart3 継承 (前回)
#ポリモーフィズムとは
ポリモーフィズムは__「多態性」「多相性」__などと訳される概念のことです。イメージとしては「ひとつの関数にたくさんの役割を与えて、その関数を呼び出す際に適したコードを実行させる」といったようなものだと思います。
カプセル化、継承、ポリモーフィズムはそれぞれオブジェクト指向の3大要素と呼ばれる大事な概念です。
ポリモーフィズムを落とし込んだプログラムはコード内で登場する関数が少なくなり、シンプルでわかりやすい記述ができます。「関数が呼ばれ方に応じて適した動作をする」ように設計にするので、ひとつひとつの関数が便利で使いやすいものと感じるようになるのではないかと思います。
###ポリモーフィズムを実現させるには
同じ名前の関数を、基底クラスと派生クラス両方に作ることを__オーバーライド__といい、同じ名前の関数で引数を変えたものを複数用意することを__オーバーロード__といいます。MQL5においてはオーバーライドとオーバーロードの両方を使ってポリモーフィズムを実現させるようです。
(MQL5公式ページより引用)
ポリモーフィズムの定義には、継承時の関数の再定義に加えて、クラス内での 1 つの関数のパラメータで異なる複数の実装が含まれます。これはクラス内に同名で型及び/またパラメータの異なる関数がいくつか存在出来ることを意味します。この場合、ポリモーフィズムは関数オーバーロードを通して実装されます。
https://www.mql5.com/ja/docs/basis/oop/polymorphism
#オーバーロードの書き方、使い方
同じ名前の関数で引数を変えたものを複数用意することをオーバーロードといいます。関数を呼び出したときの引数の違いによって、どの関数を呼ぶかを選択することができます。
// 基底クラス
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);
}
動作させてみると↓
同名の関数も引数を変えることで、どちらの関数にもアクセスすることができることがわかります。
#オーバーライドの書き方、使い方
基底クラスと派生クラスに同名の関数を宣言、定義します。__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()関数__を読んでみます。
派生クラスの__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);
}
今度はポインタを使って
- 基底クラスポインタと基底クラスオブジェクト(アドレス)を使って呼ぶ
- 基底クラスポインタと派生クラスオブジェクト(アドレス)を使って呼ぶ
の2つのパターンを試してみます。
同名で定義した__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);
}
message()関数を呼び出す側のOnInit関数では短いコードで書くことができました。このように基底クラスポインタを配列にして管理することで、どのmessage()関数も自在に実行することができます。
続きを読むときはこちら
オブジェクト指向プログラミングPart5 抽象クラス →