本カテゴリーは手続き型言語になれている人がどうやったらオブジェクト指向を理解できるのかを考えて書いている記事です。
多少の言語知識があることを前提に進めます。
さて、だんだんオブジェクト指向の確信に近づいてきました。
今回はポリモルフィズムの話です。
前の記事 でも書きましたが、継承とポリモルフィズム等
オブジェクト指向は設計が肝心
です。
継承やポリモルフィズムをいくら知っていても、使い方が分からなければ意味がありません。
どの設計を行えば効果的であるかは常々考えましょう。
(勉強したい方はUML等やデザインパターン等を学ぶと良いかもしれません。)
では始めましょう。
前回までのプログラムは
【三角クラス】
public Sankaku{ private int tyoutenAX; private int tyoutenAY; private int tyoutenBX; private int tyoutenBY private int tyoutenCX; private int tyoutenCY; // 頂点Aを設定するメソッド public void SetTyoutenA(int x, int y) { this.tyoutenAX = x; this.tyoutenAY = y; } // 頂点Bを設定するメソッド public void SetTyoutenB(int x, int y) { this.tyoutenBX = x; this.tyoutenBY = y; } // 頂点Cを設定するメソッド public void SetTyoutenC(int x, int y) { this.tyoutenCX = x; this.tyoutenCY = y; } } |
【四角クラス】
public Sikaku : Sankaku{ private int tyoutenDX; private int tyoutenDY;
// 頂点Dを設定するメソッド public void SetTyoutenD(int x, int y) { this.tyoutenDX = x; this.tyoutenDY = y; } } |
【メインクラス】
public static void main() { // 三角形一つ一つを配列として定義して考える。 Sankaku sankakuA = new Sankaku(); Sankaku sankakuB = new Sankaku(); Sankaku sankakuC = new Sankaku(); Sikaku sikakuA = new sikaku(); // 三角形には三点の頂点があるため、三つの頂点を設定する。 // データ設定 sankakuA.SetTyoutenA(100, 100); sankakuA.SetTyoutenB(50, 50); sankakuA.SetTyoutenC(50, 100); sikakuA.SetTyoutenA(100, 100); sikakuA.SetTyoutenB(100, 50); sikakuA.SetTyoutenC(50, 100); sikakuA.SetTyoutenD(50, 50); // 以下、あと二つの三角の頂点を設定する。 sankakuBの設定 sankakuCの設定 // 実際に三角を表示する。 sankakuWrite(sankakuA); sankakuWrite(sankakuB); sankakuWrite(sankakuC); // 四角形を表示するメソッドは存在しない。 } // 三角を描画する関数 // 関数に全てのデータが入っているため引数が一つになる。 public static void sankakuWrite(Sankaku sankaku) { // 頂点を元に三角形を書く関数 } |
こんな感じ(*1)でしたね。
ここで、気になるのは四角形を表示するメソッドが存在しない点です。
sankakuWriteメソッドの代わりにsikakuWriteメソッドを書いてもいいのですが、それではプログラムコードが長くなりポリモルフィズムも出来ません。
なので、各クラスに三角形を描く関数(*2)を付けてしまいます。
三角形クラスは
【三角クラス】
public Sankaku{ private int tyoutenAX; private int tyoutenAY; private int tyoutenBX; private int tyoutenBY private int tyoutenCX; private int tyoutenCY; // 頂点Aを設定するメソッド public void SetTyoutenA(int x, int y) { this.tyoutenAX = x; this.tyoutenAY = y; } // 頂点Bを設定するメソッド public void SetTyoutenB(int x, int y) { this.tyoutenBX = x; this.tyoutenBY = y; } // 頂点Cを設定するメソッド public void SetTyoutenC(int x, int y) { this.tyoutenCX = x; this.tyoutenCY = y; } // 三角を描画する関数 public void Write() { // 三角形を描く処理 } } |
このようになります。
そして、四角形クラスは
【四角クラス】
public Sikaku : Sankaku{ private int tyoutenDX; private int tyoutenDY;
// 頂点Dを設定するメソッド public void SetTyoutenD(int x, int y) { this.tyoutenDX = x; this.tyoutenDY = y; } // 四角形を描画する関数 public void Write() { // 四角形を描く処理 } } |
このようになります。
ここでのポイントは共にWriteメソッドを定義していることです。
これがオーバーライド(*3)と言う仕掛けである。
このオーバーライドは
処理を書き直す
と言うことが出来る。
まぁ、何はともあれこれを元にメインを書き換えると・・・
【メインクラス】
public static void main() { // 三角形一つ一つを配列として定義して考える。 Sankaku sankakuA = new Sankaku(); Sankaku sankakuB = new Sankaku(); Sankaku sankakuC = new Sankaku(); Sikaku sikakuA = new sikaku(); // 三角形には三点の頂点があるため、三つの頂点を設定する。 // データ設定 sankakuA.SetTyoutenA(100, 100); sankakuA.SetTyoutenB(50, 50); sankakuA.SetTyoutenC(50, 100); sikakuA.SetTyoutenA(100, 100); sikakuA.SetTyoutenB(100, 50); sikakuA.SetTyoutenC(50, 100); sikakuA.SetTyoutenD(50, 50); // 以下、あと二つの三角の頂点を設定する。 sankakuBの設定 sankakuCの設定 // 実際に三角を表示する。 sankakuA.Write(); sankakuB.Write(); sankakuC.Write(); sikakuA.Write(); } |
となる。
あ、まだここではポリモルフィズムを使っていません。
これをポリモルフィズムを使って書いてみます。
【メインクラス】
public static void main() { // 三角形一つ一つを配列として定義して考える。 Sankaku sankakuA = new Sankaku(); Sankaku sankakuB = new Sankaku(); Sankaku sankakuC = new Sankaku(); Sikaku sikakuA = new sikaku(); // 三角形には三点の頂点があるため、三つの頂点を設定する。 // データ設定 sankakuA.SetTyoutenA(100, 100); sankakuA.SetTyoutenB(50, 50); sankakuA.SetTyoutenC(50, 100); sikakuA.SetTyoutenA(100, 100); sikakuA.SetTyoutenB(100, 50); sikakuA.SetTyoutenC(50, 100); sikakuA.SetTyoutenD(50, 50); // 以下、あと二つの三角の頂点を設定する。 sankakuBの設定 sankakuCの設定 // 実際に三角を表示する。 takakuWrite(sankakuA); takakuWrite(sankakuB); takakuWrite(sankakuC); takakuWrite(sikakuA); } // 多角形を描画する関数 // 関数に全てのデータが入っているため引数が一つになる。 public static void takakuWrite(Sankaku takaku) { // 実際の描画処理は各クラスに任せる。 takaku.Write(); } |
どうですか?
違いは分かりますか?
このtakakuWriteという関数はSankakuクラスを受け取って(*4)Writeメソッドを呼んでいるだけです。
しかし、takakuWriteメソッドで処理するのは三角形か四角形か分かりません。
にもかかわらず、この処理は三角形クラスは三角形の描画を四角形クラスは四角形の描画を行ってくれます。
これは受け取ったクラスの型は関係なく実体であるクラスの処理を行うことなのです。
って、これだけではなかなか分かりにくいですね。
例えばAというマンションとBと言うマンションがあったとします。
・Aというマンションの玄関から入ってください
・Bと言うマンションの玄関から入ってください
このように言われたら問題なく玄関から入れますよね。
マンションは必ず玄関を持っています。
先ほどの三角形、四角形も継承関係なので(*5)必ずWriteメソッドを持っています。
そして、Aと言うマンションでもBと言うマンションでも
マンションの玄関から入ってください
と言えば玄関から入ることが出来ます。
同様に三角形でも四角形でもWriteメソッドを使用してくださいと言われれば各オブジェクトのWriteメソッドを呼び出すのです。
これが
ポリモルフィズム(同じ振る舞いをさせる)
なのです。
ポリモルフィズムの注意点は
キャストを行っても実体は変わらない
と言うことです。
long型をint型に変える場合は実体が変わるのでクラスのキャストも変わりそうですが、クラスのキャストは実体は変わりません。
クラスの暗黙キャストは言語によって異なります。
基本的には親クラスはひとつしかないので暗黙的にキャストできる言語が多いのですが、子クラスには暗黙的にキャストできません。注意してください。
--------------------------------------
*1・・・プログラムは理解しやすくするために書いています。正確性よりニュアンスを重視しています。
*2・・・実際に作る場合はどこに三角形を描画するかを保持しているクラスも保持させなければなりません。
*3・・・言語によって書き方が異なる。C#の場合はvirtualやoverrideと言ったキーワードを使用しなければ使えないのだが、ここでは説明のため省略。
*4・・・この関数が四角形クラスを受け取れるのは何故か分かりますよね?暗黙的に三角形クラスへキャストされているのです。
*5・・・今回のような継承関係を作る場合は親となるクラスは三角形クラスではなく図形クラスを作った方が設計はわかりやすいです。また、今回説明しませんでしたがインターフェースといった概念も存在します。これは、そのメソッドは存在するけど実体は各クラスで定義して欲しい場合に使います。(他にも抽象クラスというものも存在しますが、これは周りの設計によっては使用しない方がいいです。インターフェースで賄えるならインターフェースだけを使用するなど設計に一貫性を持たせた方が分かりやすいです。)