-
前期からの変更点
- 課題説明は「課題概要」のみで十分です。
- 今回のレポート4についても「最大3ページ」に収めるようにして下さい。
- ソースファイルも提出してもらうので、レポートに全コードコピペ掲載する必要はありません。特に工夫した点のみを掲載した上で解説・考察しよう。
-
<目次>
- 課題3の続きです。
- コード例ex-gradle2023は、コードの書き方があまりヨロシクない。ユニットテストとバージョン管理システムを使いながら、一歩ずつリファクタリングしてみよう。
- 課題レポート3を終えた状態にし、Main.javaが動作することと、EnemyTest.javaのテストが成功することを確認せよ。
- レポート報告事項
- ステップ0に関しては、pushしたリポジトリURL(Git URL)を報告するだけで良い。
- レポート3と同じリポジトリのままでも良いし、改めて別に用意しても良い。
- EnemyクラスとHeroクラスを眺めると、殆どが同一コードであることに気づくはずだ。先程 Enemy.attack() を修正したが、同じ修正コードを Hero.java にも追加記述するのはあまりヨロシクない。
- 例えば、「同じ修正内容を複数箇所に適用する」ことは可能だが、システムが大規模になるほど「適用すべき箇所」が増え、手間が増えるだけでなく、手作業のためその都度新たなバグ混入の可能性がある。これらの手間を避けるため、なるべく「同じ修正ならば、一箇所の修正で済ます」ようにコードの重複を避けることを考えた方が良い。(DRY原則)
- いろんな対応方法が考えられるが、ここでは以下のように修正してみよう。
- 両方に共通するLivingThingクラスを新規作成しよう。
- フィールド変数として下記4項目を持つものとする。
- private String name;
- private int hitPoint;
- private int attack;
- private boolean dead;
- コンストラクタとしてname, hitPoint, attackの3つを引数に取り、Enemy,Heroと同等の処理を実行しよう。(dead変数の初期化も忘れないようにしよう)
- 下記メソッドを作成。
- public boolean isDead() // deadの getter method。booleanの場合にはisを使うことが多い。
- public String getName()
- public void attack(LivingThing opponent)
- Enemyクラスではheroを攻撃、Heroクラスではenemyを攻撃するようにしていたが、共通クラスとして作成しているスーパークラスを対象に攻撃するように変更しよう。
- public void wounded(int damage)
- 出力メッセージがEnemy, Heroとで異なるが、LivingThingクラスでは「%sは倒れた。」に統一して実装しよう。
- フィールド変数として下記4項目を持つものとする。
- LivingThingを継承して、EnemyクラスとHeroクラスを実装し直そう。
- EnemyクラスとHeroクラスとも、現時点では異なる変数はないため、フィールド変数は不要である。
- コンストラクタを用意しよう。(superで親クラスのコンストラクタを呼び出そう)
- メソッドは、、、
- isDead(), getName(), attack()については同一実装となるため、LivingThing クラスに任せよう。(Enemy, Heroクラス側で実装は不要なので、削除することになる)。
- wounded() は、処理後の出力メッセージが異なる。ここでは、Enemyクラスでは「モンスター%sは倒れた。」、Heroクラスでは「勇者%sは道半ばで力尽きてしまった。」と出力するように、wounded()メソッドを上書き実装しよう。@Override アノテーションを付けること。
- 両方に共通するLivingThingクラスを新規作成しよう。
- 確認項目
- LivingThingクラス、それらを継承して作成し直したEnemyクラスとHeroクラスを修正し終えたら、Main.javaを実行してみよう。こちらは特に編集不要で、元の処理を再現できているはずである。また、attack()を親クラスで修正済みのため、enemy死亡後に攻撃してしまうことは無くなったはずである。このことをEnemyTestのテストで確認しよう。
- レポート報告事項
- (1) gradle testの結果。
- (2) 今回のコード修正を通して、気づいたことを200字以上で報告せよ。
- 動作確認を終えたらpushまでやること。
- 実装条件
- クラス名: Warrior
- 追加メソッド
- メソッド名: attackWithWeponSkill
- 戻り値: なし
- 引数: LivingThing
- 処理内容: attackの150%を固定ダメージとして与える。またメッセージを「%sの攻撃!ウェポンスキルを発動!%sに%dのダメージを与えた!!」と変更すること。
- 動作確認
- Main.javaのheroをWarriorクラスのオブジェクトwarriorに変更し、hero.attackをwarrior.attackWithWeponSkillに変更し、動作確認しよう。
- メソッド名: attackWithWeponSkill
- テスト
- WarriorTest.javaを用意し、下記内容をテストするコードを書け。
- テスト内容
- 「Warriorクラスからオブジェクトを生成し、attackWithWeponSkillを3回実行し、3回ともattackの150%ダメージになっていることを確認する」
- 備考:3回実行しているのは「念の為に複数回チェックしておこう」ぐらいの意図でやっています。回数を増やす分には構いませんが、3回未満に減らさないようにしてください。
- レポート報告事項
- (1) テストコードをそのまま掲載。
- (2) gradle test の結果。
- (3) テストコードの解説。
- 動作確認を終えたらpushまでやること。
- ステップ2を終えた時点で
Object <- Hero <- Warrior
という継承関係になっているはずだ。これまでは「Heroクラスのオブジェクトを生成したらHero型変数に代入」という形でオブジェクトを用意していた。これをHero hero = new Warrior("勇者", 10, 5);
のように WarriorオブジェクトをスーパークラスであるHero型変数に代入 するとどうなるだろうか。このことを以下のコードで確認しよう。 - ファイル名: Main2.java
//ファイル名: Main2.java
import jp.ac.uryukyu.ie.tnal.*;
public class Main2 {
public static void main(String[] args){
Hero hero = new Warrior("勇者", 10, 5); // (a)
Enemy enemy = new Enemy("スライム", 6, 3);
System.out.printf("%s vs. %s\n", hero.name, enemy.name);
int turn = 0;
while( hero.dead == false && enemy.dead == false ){
turn++;
System.out.printf("%dターン目開始!\n", turn);
hero.attack(enemy); // (b)
enemy.attack(hero);
}
System.out.println("戦闘終了");
}
}
- 確認項目
- (1) コード内の(a)行はサブクラスのオブジェクトを生成し、スーパークラス型の変数に代入している。このコードを実行できるだろうか?
- (2) コード内の(b)行は、サブクラスのオブジェクトを保存した状態のHero型変数の持つメソッドattackを呼び出している。このコードは実行できるだろうか?
- (3) (a)行のスーパークラスとサブクラスを逆にするとどうなるだろうか?
- (4) (a)行を元に戻し、(b)行を
hero.attackWithWeponSkill(enemy);
にするとどうなるだろうか? - (5) オブジェクトhero を用意したあとで
System.out.println(hero);
と記述して実行せよ。どのような出力になるだろうか?
- レポート報告事項
- (1)〜(4)については「できた・できなかった」のいずれかを回答すること。できなかった場合にはその際のエラーも掲載すること。
- (1)〜(4)の結果を踏まえ、継承関係にあるクラスのオブジェクトを扱う際に「できること」と「できないこと」を整理して解説せよ。
- (5)については出力結果を報告せよ。また、System.out.printlnをAPIドキュメントで調べ、何故今回の出力が得られたのかを説明せよ。
- 動作確認を終えたらpushまでやること。
- ペアや友人らと話し合って取り組んで構わないが、コード解説を加えるなど「自分自身の報告書」となるように取り組むこと。試して分かったこと、自身で解決できなかった部分等についてどう取り組んだか、といった過程がわかるように示すこと。(考えを図表や文章を駆使して表現して報告する練習です)
- レポート作成は好きなツール(ソフトウェア)を使って構わない。ただし下記を含めること。
- タイトル
- 今回は「プログラミング2、レポート課題4: 「リファクタリングを通して継承に慣れよう」」。
- 提出日: yyyy-mm-dd
- 報告者: 学籍番号、氏名
- 複数人で相談しながらやった場合、相談者らを「協力者: 学籍番号、氏名」として示そう。
- 課題説明(概要のみでOK)
- Step 0について
- pushしたリポジトリURL(Git URL)を報告すること。
- Step 1について
- 指定手順で得られる gradle testの結果と、今回の修正で気づいたこと。
- Step 2について
- テストコード、テスト結果、テストコードの解説。
- その他
- 通常は感想等をレポートには含めませんが、練習なので課題に取り組みながら何か感じたこと、悩んでいること等、書きたいことがあれば自由に書いてください。(なければ省略OK)
- タイトル
- 提出物は「レポート」の1点である。
- ソースファイルの提出は不要。(GitHubで確認します)
- レポートは電子ファイルで提出するものとする。
- 提出先:
- Google ドキュメントのreport4。
- 締切: 調整中。