同じような処理
次のように乗り物の乗り方を説明するプログラムを作成しました。例えば「1:準備」を選択すると各乗り物の準備方法の説明がはじまる仕組みです。(メソッド内の処理内容は省略しています)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
package jp.ayax.object; import java.util.Scanner; public class Main6 { public static void main(String[] args) { Bicycle bicycle = new Bicycle(); Car car = new Car(); System.out.println("1:準備"); System.out.println("2:スタート"); System.out.println("3:スピードアップ"); System.out.println("4:スピードダウン"); System.out.println("5:ストップ"); Scanner scan = new Scanner(System.in); String input = scan.nextLine(); switch(input){ case "1": bicycle.print(); bicycle.ready(); car.print(); car.ready(); break; case "2": bicycle.print(); bicycle.start(); car.print(); car.start(); break; case "3": bicycle.print(); bicycle.up(); car.print(); car.up(); break; case "4": bicycle.print(); bicycle.down(); car.print(); car.down(); break; case "5": bicycle.print(); bicycle.stop(); car.print(); car.ready(); break; } } } class Bicycle { String name = "自転車"; public void ready(){ System.out.println("サドルに乗り、効き足をペダルにのせます。");//本当はもっと複雑な処理です。 } public void start(){ System.out.println("効き足でペダルを押し込みます。");//本当はもっと複雑な処理です。 } public void up(){ System.out.println("両足でペダルを必死に回します。");//本当はもっと複雑な処理です。 } public void down(){ System.out.println("ペダルを回すのを止めます。");//本当はもっと複雑な処理です。 } public void stop(){ System.out.println("両手でブレーキを握ります。");//本当はもっと複雑な処理です。 } public void print(){ System.out.println(name); } } class Car { String name = "自動車"; public void ready(){ System.out.println("キーを指します。");//本当はもっと複雑な処理です。 } public void start(){ System.out.println("アクセルペダルをゆっくり少しだけ踏み込みます。");//本当はもっと複雑な処理です。 } public void up(){ System.out.println("アクセルペダルを踏みこみます。");//本当はもっと複雑な処理です。 } public void down(){ System.out.println("アクセルペダルを戻します。");//本当はもっと複雑な処理です。 } public void stop(){ System.out.println("ブレーキペダルを踏みます。");//本当はもっと複雑な処理です。 } public void print(){ System.out.println(name); } } |
これからバイクの乗り方、竹馬の乗り方、H3ロケットの乗り方・・・とシリーズ化していく予定です。各シリーズのメソッドは上記のメソッド(ready、start・・)と同名にする予定ですが、処理内容が違うため共通化するのは難しいです。そのためシリーズが増えるにつれクラスが増えてしまうことになります。しかし、18行目~49行目の各シリーズ毎のメソッド呼出しが増えていくのは避けたいです。何かいい方法はないでしょうか?
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
switch(input){ case "1": bicycle.print(); bicycle.ready(); car.print(); car.ready(); break; case "2": bicycle.print(); bicycle.start(); car.print(); car.start(); break; case "3": bicycle.print(); bicycle.up(); car.print(); car.up(); break; case "4": bicycle.print(); bicycle.down(); car.print(); car.down(); break; case "5": bicycle.print(); bicycle.stop(); car.print(); car.ready(); break; } |
複合技
いいアイディアが浮かんだ人もいるかと思います。今回は次のようにプログラムを変更しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
package jp.ayax.object; import java.util.Scanner; public class Main { public static void main(String[] args) { Vehicle[] v = { new Bicycle(), new Car(), }; System.out.println("1:準備"); System.out.println("2:スタート"); System.out.println("3:スピードアップ"); System.out.println("4:スピードダウン"); System.out.println("5:ストップ"); Scanner scan = new Scanner(System.in); String input = scan.nextLine(); for(int i=0;i<v.length;i++){ v[i].print(); switch(input){ case "1": v[i].ready(); break; case "2": v[i].start(); break; case "3": v[i].up(); break; case "4": v[i].down(); break; case "5": v[i].stop(); break; } } } } abstract class Vehicle{ abstract public void ready(); abstract public void start(); abstract public void up(); abstract public void down(); abstract public void stop(); abstract public void print(); } class Bicycle extends Vehicle{ String name = "自転車"; @Override public void ready() { System.out.println("サドルに乗り、効き足をペダルにのせます。");//本当はもっと複雑な処理です。 } @Override public void start() { System.out.println("効き足でペダルを押し込みます。");//本当はもっと複雑な処理です。 } @Override public void up() { System.out.println("効き足でペダルを押し込みます。");//本当はもっと複雑な処理です。 } @Override public void down() { System.out.println("ペダルを回すのを止めます。");//本当はもっと複雑な処理です。 } @Override public void stop() { System.out.println("両手でブレーキを握ります。");//本当はもっと複雑な処理です。 } @Override public void print() { System.out.println(name); } } class Car extends Vehicle{ String name = "自動車"; @Override public void ready(){ System.out.println("キーを指します。");//本当はもっと複雑な処理です。 } @Override public void start(){ System.out.println("アクセルペダルをゆっくり少しだけ踏み込みます。");//本当はもっと複雑な処理です。 } @Override public void up(){ System.out.println("アクセルペダルを踏みこみます。");//本当はもっと複雑な処理です。 } @Override public void down(){ System.out.println("アクセルペダルを戻します。");//本当はもっと複雑な処理です。 } @Override public void stop(){ System.out.println("ブレーキペダルを踏みます。");//本当はもっと複雑な処理です。 } @Override public void print(){ System.out.println(name); } } |
まずは各シリーズのベースとなるスーパークラスVehicleを作成しました。これはabstractクラスとなっており各シリーズのクラスに同名のメソッドを強制的に作成させます。これで確実に同じ名前のメソッドが各シリーズ用意されことになります。
44 45 46 47 48 49 50 51 |
abstract class Vehicle{ abstract public void ready(); abstract public void start(); abstract public void up(); abstract public void down(); abstract public void stop(); abstract public void print(); } |
次にメイン処理の8行目でスーパークラスVehicleの配列をシリーズ分作成しています。
8 9 10 |
Vehicle[] v = { new Bicycle(), new Car(), }; |
そしてスーパークラスVehicleの配列にシリーズ毎の(Bicycle、Car)インスタンスを格納します。つまり、サブクラスからスーパークラスにキャストしています。これで各シリーズのクラスを1つの配列で管理できます。
クラスが配列化されましたので、20行目~39行目の処理で、シリーズ分ループしながら各クラスのメソッドを呼び出しています。そうです、各クラス毎のメソッド呼出しを記述する必要がなくなったのです。これでシリーズが増えていってもこの部分のコードが長くなることはありません。
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
for(int i=0;i<v.length;i++){ v[i].print(); switch(input){ case "1": v[i].ready(); break; case "2": v[i].start(); break; case "3": v[i].up(); break; case "4": v[i].down(); break; case "5": v[i].stop(); break; } } |
ポリモーフィズム(多様性/多態性)
今回のポイントはオブジェクト指向のポリモーフィズム(多様性/多態性)を利用していることです。ポリモーフィズム(多様性/多態性)とは次の通りです。(ウィキペディアより)
異なる種類のオブジェクトに同一の操作インターフェースを持たせる仕組みが多態性と呼ばれる。オブジェクト指向下の多態性は、クラスの派生関係またはオブジェクトの動的バインディングを利用した実行時変化プロセスであるサブタイピング(英語版)を指す。サブタイピング(派生法)は仮想関数(英語版)、多重ディスパッチ、動的ディスパッチ(英語版)の三手法に分類される。最もよく知られる仮想関数は多態性と同義に説明される事が多い。仮想関数は、メソッドが所属するクラスの派生関係のみに焦点を当てたシングルディスパッチであり、スーパークラス抽象メソッドへの呼び出しから、その実行時のサブクラス実装メソッドに多方向分岐させるプロセスを指す。その際はVtableと呼ばれる関数へのポインタに近い仕組みが用いられる。抽象メソッドの使用は依存性逆転の原則に準じたものであり、それを中継点にした具体的メソッドの実装は開放閉鎖の原則を体現するものである。多重ディスパッチは、メソッドが所属するクラスの派生関係に加えて、メソッドの各引数のクラスの派生関係にも注目した形態である。各引数は実行時に型ダウンキャストされて、その引数の型パターンに対応したルーチンに枝分かれする。前述の仮想関数による枝分かれがその前に入る形態もあれば、入らない形態もある。入らない場合は単一引数だとシングルディスパッチになる。多重ディスパッチの中でもプロセス分岐に関与するクラスが二つに限定されたものはダブルディスパッチと個別定義されている。動的ディスパッチは、プロトタイプベースのOOPで用いられるものであり、実行時におけるオブジェクトのメソッド参照の切り替えカスタマイズによるプロセス変化を指す。また、クラスベースのOOPでもクラスの定義内容を操作できるリフレクション機能によってこれが再現される事もある。
えーと・・・、つまり「v[i].ready();」と記述すると、同じ記述なのにbicycleクラスやcarクラスのready()メソッドが動作するのですよ、ってことです。