# インターフェースの意義 ## 回答したいこと - 疑問1: インターフェースを使うメリットとは? - A: コードを整理し、保守性を高められる - インターフェースを変数の型として使うことで、同じインターフェースを実装するクラスのインスタンスなら差し替えても同じようにインスタンスメソッドの呼び出しが可能になる(ポリモーフィズム) - 例えば本ページ下部記載の題材だと、startBattleGameメソッド中のplayer変数・enemy変数の値をIBattlableを実装するクラス(Ghost, Slime)のインスタンスで差し替えても同じようにisLivingメソッド・attackメソッドの呼び出しができている。 - ポリモーフィズムを使わない場合、冗長な書き方になる(本ページの下部にポリモーフィズムを利用しないで書いたstartBattleGameメソッドを記載済)。 - 今後新規キャラクターとしてキラーマシンやメタルスライムについてクラスを追加したとしても、ゲーム部分の改修はselectBattlableCharacterメソッドのif文の分岐を追加するだけでよい。 - A: 作業を分担し、効率よく開発を進められる - 題材(本ページの下部記載)だと、一旦IBattlableインターフェースを作ってしまえば、バトルゲーム用のキャラクタークラスの開発を同時並行で進められる。 - AさんはIBattlableインターフェースに基づいてGhostクラスを実装 - BさんはIBattlableインターフェースに基づいてSlimeクラスを実装 - CさんはIBattlableインターフェースに基づいてKillerMachineクラスを実装 - DさんはIBattlableインターフェースに基づいてMetalSlimeクラスを実装 - ... - 疑問2: インターフェースをクラスに実装させた時と同じメリットは継承を使っても得られるが、なぜインターフェースを使うのか? - A: ソースコードの保守性・技術的制約を考慮したときに、インターフェースを使うのがベストな場合があるから。 - 例として、題材のIBattlableインターフェース・IRunnableインターフェースをクラスで置き換えられないことを示す。 - 案1: IBattlableのメソッドの実装を持つクラス(Battlable)を作ってIBattlableを置き換え、IRunnableのメソッドの実装を持つクラス(Runnable)でIRunnnableを置き換えるのはどうか?(結果的にSlimeクラスがCreature、Battlable、Runnnableを同時に継承することになる) - A:javaでは2つ以上のクラスを同時に継承できないため不可能。 - 案2: Creature、Ghostの親クラスとしてBaseクラスを定義し、Baseクラス内でIBattlable・IRunnableのメソッド(attack, damage, run)を定義し、IBattlable・IRunnnableをBaseで置き換えるのはどうか? - A: その場合、Baseクラスに色んなメソッド(attack, damage, run)を含めてしまっているため、ゴーストは走れないはずなのにBaseを継承するゴーストクラスはrunメソッドを持つことになるなど、仕様上の概念とコード上での表現がズレてしまい、保守性が下がってしまう。 - 以上から、本ケースの場合IBattlable、IRunnableインターフェースをクラスで置き換えられない。 ## 題材 DQ風ターン制バトルゲーム&徒競走ゲーム ## 仕様(ざっくり) - ①ターン制バトル②徒競走の2つのゲームが出来る - 最初にどちらかのゲームを選択する。 - ターン制バトルについて - 最初に自分と相手のキャラクターを選ぶ - 自分のキャラと相手のキャラが交互に攻撃する - 相手のキャラが倒れれば勝ち - 徒競走について - 最初に自分と相手のキャラクターを選ぶ - 自分のキャラが相手のキャラより走りが速ければ勝ち - キャラクターについて - 下記の3キャラクターがいる - ヒューマン - 走れる(速さ100) - ゴースト - バトルできる(攻撃力10、ダメージを受けない、10回攻撃を受けたら倒れる) - スライム - 走れる(速さ10) - バトルできる(hp10、攻撃力10、hpが0になったら倒れる) ## ソースコード(ざっくり) ``` interface IRunnable{ int run(); } interface IBattlable{//名前がダサい boolean isLiving(); void attack(IBattlable ibattlable); void damage(int point); } class Ghost implements IBattlable{ int spirit = 10; int attack = 1; boolean isLiving(){ return 0 < spirit; } void attack(IBattlable ibattlable){ monster.damage(attack); } void damage(int point){ spirit = spirit - 1; } } class Creature{ int hp=100; boolean isLiving(){ return 0 < hp; } } class Human extends Creature implements IRunnable{ int speed = 100; int run(){ return speed; } } class Slime extends Creature implements IBattlable, IRunnable{ Slime(){ super(); hp = 10; attack = 10; speed = 10; } void attack(IBattlable ibattlable){ monster.damage(attack); } void damage(int point){ hp = hp - point; } int run(){ return speed; } } class Game{ void main(){ System.out.println('ゲームを選んでください(バトルゲームなら0、徒競走ゲームなら1):'); int mode = new Scanner.scanInt(); if(mode==0){ startBattleGame(); }else if(mode==1){ startRunGame(); } } void startBattleGame(){ System.out.println("バトルゲームを始めます"); IBattlable player = selectBattlableCharacter(); IBattlable enemy = selectBattlableCharacter(); while( player.isLiving() && enemy.isLiving() ){ player.attack(enemy); enemy.attack(player); } if(player.isLiving()){ System.out.println("プレイヤーの勝利"); }else{ System.out.println("敵の勝利"); } } void startRunGame(){ System.out.println("徒競走ゲームを始めます"); IRunnable player = selectRunnableCharacter(); IRunnable enemy = selectRunnableCharacter(); if( enemy.run() < player.run()){ System.out.println("プレイヤーの勝利"); }else{ System.out.println("敵の勝利"); } } IRunnable selectRunnableCharacter(){ System.out.println("キャラ選択(スライム or ヒューマン):"); String str = new Scanner.scan(); if(str.equals("スライム")){ return new Slime(); }else{ return new Human(); } } IBattlable selectBattlableCharacter(){ System.out.println("キャラ選択(スライム or ゴースト):"); String str = new Scanner.scan(); if(str.equals("スライム")){ return new Slime(); }else { return new Ghost(); } } } ``` ## startBattleGame(ポリモーフィズムなし) ``` void startBattleGame(){ System.out.println("バトルゲームを始めます"); System.out.println("キャラ選択(スライム or ゴースト):"); String str = new Scanner.scan(); if(str.equals("スライム")){ Slime player = new Slime(); System.out.println("キャラ選択(スライム or ゴースト):"); String str = new Scanner.scan(); if(str.equals("スライム")){ Slime enemy = new Slime(); while( player.isLiving() && enemy.isLiving() ){ player.attack(enemy); enemy.attack(player); } if(player.isLiving()){ System.out.println("プレイヤーの勝利"); }else{ System.out.println("敵の勝利"); } }else { Ghost enemy = new Ghost(); while( player.isLiving() && enemy.isLiving() ){ player.attack(enemy); enemy.attack(player); } if(player.isLiving()){ System.out.println("プレイヤーの勝利"); }else{ System.out.println("敵の勝利"); } } }else { Ghost player = new Ghost(); System.out.println("キャラ選択(スライム or ゴースト):"); String str = new Scanner.scan(); if(str.equals("スライム")){ Slime enemy = new Slime(); while( player.isLiving() && enemy.isLiving() ){ player.attack(enemy); enemy.attack(player); } if(player.isLiving()){ System.out.println("プレイヤーの勝利"); }else{ System.out.println("敵の勝利"); } }else { Ghost enemy = new Ghost(); while( player.isLiving() && enemy.isLiving() ){ player.attack(enemy); enemy.attack(player); } if(player.isLiving()){ System.out.println("プレイヤーの勝利"); }else{ System.out.println("敵の勝利"); } } } } ```