Skip to content

Latest commit

 

History

History
159 lines (137 loc) · 11.5 KB

report4_refactoring2_inheritance.md

File metadata and controls

159 lines (137 loc) · 11.5 KB

課題レポート4: リファクタリングを通して継承に慣れよう


  • 課題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は倒れた。」に統一して実装しよう。
    • LivingThingを継承して、EnemyクラスとHeroクラスを実装し直そう。
      • EnemyクラスとHeroクラスとも、現時点では異なる変数はないため、フィールド変数は不要である。
      • コンストラクタを用意しよう。(superで親クラスのコンストラクタを呼び出そう)
      • メソッドは、、、
        • isDead(), getName(), attack()については同一実装となるため、LivingThing クラスに任せよう。(Enemy, Heroクラス側で実装は不要なので、削除することになる)。
        • wounded() は、処理後の出力メッセージが異なる。ここでは、Enemyクラスでは「モンスター%sは倒れた。」、Heroクラスでは「勇者%sは道半ばで力尽きてしまった。」と出力するように、wounded()メソッドを上書き実装しよう。@Override アノテーションを付けること。
  • 確認項目
    • 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に変更し、動作確認しよう。
  • テスト
    • 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。
  • 締切: 調整中。