5日目のゴール
5日目のテーマは
「コンストラクタで“安全に生まれる”だけでなく、“読みやすく・意図が伝わる初期化”を設計すること」 です。
ここまでであなたはすでに、
コンストラクタで必須情報を受け取る
おかしな値をコンストラクタで弾く・補正する
関連するオブジェクトもコンストラクタの中で一緒に初期化する
というところまで来ています。
5日目ではここから一歩進んで、
「new の書き方を見ただけで、どんな状態で生まれるか想像できる」
「main のコードが“設定だらけ”にならないようにする」
「初期化のルールを、1か所にまとめて“宣言的”に書く」
この感覚を、少しだけ本格的な「ゲームキャラ+装備+難易度設定」のミニアプリで固めていきます。
今日の題材:ゲームキャラの初期化を“ちゃんと設計する”
現実のイメージから整理する
ゲームをイメージしてみます。
キャラクターがいる
キャラクターには名前・HP・攻撃力がある
武器を装備している
ゲーム全体の難易度によって、ステータスが変わる
これをクラスにすると、登場人物はこうなります。
Character … キャラクターそのもの
Weapon … 武器(攻撃力を持つ)
GameConfig … ゲーム全体の設定(難易度など)
Main … それらを new して、シナリオを書く
5日目では、
「Character を new した瞬間に、Weapon や GameConfig の影響も含めて“意味のある状態”で生まれる」
という形を目指します。
Weapon クラスを先に“きれいに”しておく
シンプルだけど、ちゃんと初期化されるクラス
まずは武器から。
public class Weapon {
String name;
int attack;
Weapon(String name, int attack) {
if (name == null || name.isEmpty()) {
System.out.println("武器名が空です。'木の棒' にします。");
this.name = "木の棒";
} else {
this.name = name;
}
if (attack <= 0) {
System.out.println("攻撃力が 0 以下です。1 にします。");
this.attack = 1;
} else {
this.attack = attack;
}
}
void show() {
System.out.println("武器: " + name + " (攻撃力: " + attack + ")");
}
}
Javaここでやっていることは、これまでと同じです。
必須情報(名前・攻撃力)をコンストラクタで受け取る
おかしな値(空文字・0以下)を補正する
new した瞬間に「とりあえず使える武器」が必ずできる
Weapon 単体としては、これで十分です。
GameConfig で「難易度」というルールをまとめる
main にバラバラに書かないための“設定クラス”
次に、「難易度によってステータスを変えたい」というルールを、
GameConfig というクラスにまとめます。
public class GameConfig {
String difficulty; // "normal" や "hard" など
double hpMultiplier;
double attackMultiplier;
GameConfig(String difficulty) {
if (difficulty == null) {
difficulty = "normal";
}
if (difficulty.equals("hard")) {
this.difficulty = "hard";
this.hpMultiplier = 0.8;
this.attackMultiplier = 1.2;
} else {
this.difficulty = "normal";
this.hpMultiplier = 1.0;
this.attackMultiplier = 1.0;
}
}
void show() {
System.out.println("難易度: " + difficulty);
System.out.println("HP倍率: " + hpMultiplier);
System.out.println("攻撃力倍率: " + attackMultiplier);
}
}
Javaここでのポイントは、
難易度に応じた「HP と攻撃力の倍率」を、このクラスに閉じ込めている
main に「ハードなら HP を 0.8 倍にして…」などと書かせないnew GameConfig("hard") と書くだけで、「ハードのルール」が手に入る
ということです。
初期化の意味をもう一度言い換えると、
「このアプリの“前提となるルール”を、クラスとして形にすること」
でもあります。
Character を“全部まとめて初期化する窓口”にする
名前・基礎ステータス・武器・難易度を一気に反映する
いよいよ Character です。
public class Character {
String name;
int baseHp;
int baseAttack;
Weapon weapon;
GameConfig config;
int hp;
int attack;
Character(String name, int baseHp, int baseAttack, Weapon weapon, GameConfig config) {
if (name == null || name.isEmpty()) {
System.out.println("キャラ名が空です。'ななし' にします。");
this.name = "ななし";
} else {
this.name = name;
}
if (baseHp <= 0) {
System.out.println("基礎HP が 0 以下です。50 にします。");
this.baseHp = 50;
} else {
this.baseHp = baseHp;
}
if (baseAttack <= 0) {
System.out.println("基礎攻撃力 が 0 以下です。5 にします。");
this.baseAttack = 5;
} else {
this.baseAttack = baseAttack;
}
if (weapon == null) {
System.out.println("武器が null です。デフォルト武器を装備します。");
this.weapon = new Weapon("木の棒", 1);
} else {
this.weapon = weapon;
}
if (config == null) {
System.out.println("GameConfig が null です。normal として扱います。");
this.config = new GameConfig("normal");
} else {
this.config = config;
}
double hpCalc = this.baseHp * this.config.hpMultiplier;
this.hp = (int) hpCalc;
double attackCalc = (this.baseAttack + this.weapon.attack) * this.config.attackMultiplier;
this.attack = (int) attackCalc;
}
void showStatus() {
System.out.println("=== キャラクター情報 ===");
System.out.println("名前: " + name);
System.out.println("基礎HP: " + baseHp);
System.out.println("基礎攻撃力: " + baseAttack);
weapon.show();
System.out.println("--- 難易度設定 ---");
config.show();
System.out.println("--- 実際のステータス ---");
System.out.println("HP: " + hp);
System.out.println("攻撃力: " + attack);
}
}
Javaここは少し長いですが、やっていることは一貫しています。
名前・基礎HP・基礎攻撃力をチェックして補正する
武器が null なら、デフォルト武器を new する
GameConfig が null なら、normal として new する
最後に、「基礎ステータス × 難易度倍率 + 武器」を使って、実際の hp・attack を計算する
つまり、
new Character(...) と書いた瞬間に、
「難易度と武器を反映した、実際に戦えるキャラ」が完成する
という状態になっています。
main から見た“初期化の読みやすさ”を確認する
new の1行で「意図」が伝わるか?
ここまでのクラスを main から使ってみます。
public class Main {
public static void main(String[] args) {
GameConfig normalConfig = new GameConfig("normal");
GameConfig hardConfig = new GameConfig("hard");
Weapon sword = new Weapon("鉄の剣", 10);
Weapon dagger = new Weapon("短剣", 5);
Character c1 = new Character("勇者", 100, 10, sword, normalConfig);
Character c2 = new Character("盗賊", 80, 8, dagger, hardConfig);
Character c3 = new Character("", -10, 0, null, null);
c1.showStatus();
System.out.println("-----");
c2.showStatus();
System.out.println("-----");
c3.showStatus();
}
}
Javaこの main を眺めてみてください。
new GameConfig("hard") を見れば、「ハードモードだな」と分かるnew Weapon("鉄の剣", 10) を見れば、「攻撃力10の剣だな」と分かるnew Character("盗賊", 80, 8, dagger, hardConfig) を見れば、
「盗賊・基礎HP80・基礎攻撃8・短剣装備・ハードモード」という意図が一目で分かる
ここが、5日目で一番見てほしいポイントです。
「コンストラクタの引数の並びが、そのまま“初期状態の説明”になっているか?」
という視点です。
初期化を“宣言的”に書くという感覚
「どう計算しているか」より「何を指定しているか」が見えるように
Character の中では、
HP や攻撃力の計算をゴリゴリやっています。
でも、main からはその計算式は見えません。
見えるのは、
どんな難易度か
どんな武器か
どんな基礎ステータスか
だけです。
これは、
「計算やチェックの細かいロジックはコンストラクタの中に閉じ込めて、
呼び出し側には“何を指定しているか”だけを見せる」
という設計です。
初期化の意味を、もう一段深く言うなら、
「オブジェクトの“最初の姿”を、new の1行で宣言できるようにすること」
とも言えます。
5日目で意識してほしい“3つの問い”
今日のコードを通して、
コンストラクタを書くときに、こんな問いを自分に投げてほしいです。
このクラスは、どんな情報を渡されたら“意味のある状態”で生まれてこられるか?
その情報を、コンストラクタの引数として素直に並べられているか?
new の1行を見ただけで、「このオブジェクトはどんな初期状態か」が想像できるか?
もし「はい」と言えるなら、
そのコンストラクタはかなり良い設計になっています。
5日目のミニアプリをもう一度まとめて眺める
全体像を頭の中でつなげる
Weapon
「武器名」と「攻撃力」を持ち、
おかしな値を補正しながら初期化される。
GameConfig
「難易度」に応じて、HP と攻撃力の倍率を決める。
難易度のルールを1か所に閉じ込める。
Character
名前・基礎ステータス・武器・難易度設定を受け取り、
チェックと補正を行い、
最終的な HP と攻撃力を計算して持つ。new Character(...) の1行で、「戦えるキャラ」が完成する。
Main
これらを new して、
「どんな難易度で」「どんな武器を持った」「どんなキャラ」を作るかを宣言するだけ。
この構造を、自分の言葉で説明できたら、5日目はかなりいい仕上がりです。
5日目で絶対に押さえてほしい本質
今日いちばん大事なのは、
「初期化=“安全”だけでなく、“意図が読み取れる形”にすること」
という感覚を持てたかどうかです。
コンストラクタでやるべきことは、
必須情報を引数として素直に受け取る
おかしな値をその場で補正する
関連するオブジェクトも一緒に初期化する
new の1行が、そのオブジェクトの“自己紹介”になっているようにする
そして、
new した瞬間に、もう安心して使えるnew の1行を見ただけで、どんな初期状態かイメージできる
ここまで来たあなたは、
コンストラクタを「文法」ではなく、
“オブジェクトの生まれ方をデザインする道具”として扱えている 状態です。
6日目・7日目では、
この感覚をもっと大きなアプリ構成や、
設定ファイル風のクラス、シーン切り替えなどにも広げていくと、
設計のセンスが一気に伸びていきます。


