ピカチュウをコードで表現してみよう
1. はじめに:なぜポケモンで学ぶのか?
オブジェクト指向(OO:Object Oriented)は「現実世界をコードで表現する」考え方です。
そしてポケモンの世界は実はOOの概念だらけです。
- ポケモン図鑑→クラス(設計図)
- 実際のポケモン→インスタンス(実体)
- 個体値→カプセル化(隠蔽されたデータ)
- どのポケモンも「鳴く」→ポリモーフィズム
今回は「ピカチュウを1匹作る」ところから始めて、OOの基礎を体験していきましょう。
2. クラスとインスタンス
図鑑=クラス
個体=インスタンス
ポケモン図鑑には「ピカチュウとはこういうポケモンである」という定義が書いてあります。
これがクラスです。
そして、サトシが実際に連れているピカチュウは、その定義から生まれた「個体」です。
// ポケモン図鑑No.25の「設計図」
class Pikachu {
name: string;
level: number;
hp: number;
constructor(level: number) {
this.name = "ピカチュウ";
this.level = level;
this.hp = 35 + level; // レベルに応じたHP
}
}
// サトシのピカチュウ(個体)
const satoshiPikachu = new Pikachu(50);
console.log(satoshiPikachu.level); // 50
// 野生のピカチュウ(別個体)
const wildPikachu = new Pikachu(5);
console.log(wildPikachu.level); // 5
同じ「ピカチュウ」でも、サトシのピカチュウとトキワの森の野生のピカチュウは別物です。
設計図(クラス)は同じだけど、個体(インスタンス)ごとにレベルや状態が違います。
3. カプセル化:個体値は誰にも見せない
ポケモンには「個体値」という隠しステータスがあります。
ゲーム内では直接見ることができず、ジャッジ機能や外部ツールを使わないとわかりません。
また、HPも勝手に書き換えられたら困りますよね。ダメージを受けたり回復したり、決まった手順を踏んで変化するべきです。
このように「外から直接触らせたくないデータを隠し、決まった方法でだけ操作させる」のがカプセル化です。
class Pokemon {
public name: string;
private individualValue: number; // 個体値(外部からアクセス不可)
private currentHp: number;
private maxHp: number;
constructor(name: string, maxHp: number) {
this.name = name;
this.maxHp = maxHp;
this.currentHp = maxHp;
this.individualValue = Math.floor(Math.random() * 32); // 0〜31でランダム
}
// HPを見るのはOK
get hp(): number {
return this.currentHp;
}
// ダメージを受ける(HPを減らす唯一の方法)
receiveDamage(damage: number): void {
this.currentHp = Math.max(0, this.currentHp - damage);
if (this.currentHp === 0) {
console.log(`${this.name}は倒れた...`);
}
}
// 回復する
heal(amount: number): void {
this.currentHp = Math.min(this.maxHp, this.currentHp + amount);
console.log(`${this.name}のHPが回復した!`);
}
// ジャッジ機能(個体値の判定だけ公開)
judge(): string {
if (this.individualValue === 31) return "さいこう";
if (this.individualValue >= 26) return "すばらしい";
if (this.individualValue >= 16) return "かなりいい";
return "まあまあ";
}
}
const pikachu = new Pokemon("ピカチュウ", 100);
// ✅ 許可された操作
pikachu.receiveDamage(30);
console.log(pikachu.hp); // 70
console.log(pikachu.judge()); // "かなりいい" など
// ❌ これはできない(コンパイルエラー)
// pikachu.currentHp = 9999; // チート不可!
// pikachu.individualValue = 31; // 個体値改ざん不可!
なぜカプセル化が必要でしょうか?
-
currentHp = -100のような不正な値を防げる - HP変更時に「瀕死判定」などのロジックを必ず通せる
- 内部実装を変えても、外部のコードに影響しない
4. 継承:ポケモンの共通点をまとめる
ピカチュウもイーブイも「ポケモン」です。
共通する性質は親クラスにまとめましょう。
abstract class Pokemon {
constructor(
public name: string,
protected level: number,
protected hp: number
) {}
// 全ポケモン共通の処理
showStatus(): void {
console.log(`${this.name} Lv.${this.level} HP:${this.hp}`);
}
// 鳴き声は各ポケモンで違う → 抽象メソッド
abstract cry(): void;
}
class Pikachu extends Pokemon {
constructor(level: number) {
super("ピカチュウ", level, 35 + level);
}
cry(): void {
console.log("ピカチュウ!");
}
// ピカチュウ固有の技
thunderbolt(): void {
console.log("10まんボルト!");
}
}
class Eevee extends Pokemon {
constructor(level: number) {
super("イーブイ", level, 55 + level);
}
cry(): void {
console.log("ブイ!");
}
}
const pikachu = new Pikachu(25);
const eevee = new Eevee(10);
pikachu.showStatus(); // ピカチュウ Lv.25 HP:60
pikachu.cry(); // ピカチュウ!
eevee.showStatus(); // イーブイ Lv.10 HP:65
eevee.cry(); // ブイ!
ポイント
-
abstract classは直接インスタンス化できない(new Pokemon()は不可) -
abstract cry()は「鳴けることは決まっているが、鳴き方は各自で決めて」という宣言 - 共通処理(
showStatus)は親が書いておけば、子は書かなくて良い
5. ポリモーフィズム:ポケモンならみんな鳴ける
ポケモンセンターでは、どんなポケモンが来ても対応できます。
ピカチュウ専用の対応、イーブイ専用の対応と個別に用意する必要はありません。
function pokemonCenterGreeting(pokemon: Pokemon): void {
console.log("ようこそ!ポケモンセンターへ");
pokemon.cry(); // どのポケモンでもOK
pokemon.showStatus();
}
// どのポケモンを渡しても動く
pokemonCenterGreeting(new Pikachu(25));
// ようこそ!ポケモンセンターへ
// ピカチュウ!
// ピカチュウ Lv.25 HP:60
pokemonCenterGreeting(new Eevee(10));
// ようこそ!ポケモンセンターへ
// ブイ!
// イーブイ Lv.10 HP:65
これが ポリモーフィズム(多態性) です。
pokemonCenterGreetingが親クラスであるPokemonを受け取っているところがポイントです。
// ポケモンの配列も作れる
const party: Pokemon[] = [
new Pikachu(50),
new Eevee(45),
new Pikachu(30), // ピカチュウ2匹目もOK
];
// 手持ち全員の鳴き声
party.forEach(pokemon => pokemon.cry());
// ピカチュウ!
// ブイ!
// ピカチュウ!
ポイント
- 呼び出す側は「ポケモンは鳴ける」という共通ルールだけ知っていればOK
- 中身がピカチュウでもイーブイでも気にしない
6. まとめ
| 概念 | ポケモンでの例 | TypeScriptでの表現 |
|---|---|---|
| クラス | ポケモン図鑑の定義 | class Pokemon {} |
| インスタンス | 実際のポケモン個体 | new Pikachu(25) |
| カプセル化 | 個体値の隠蔽 |
private, getter/setter |
| 継承 | 全ポケモン共通の性質 |
extends, abstract
|
| ポリモーフィズム | 「ポケモン」として統一的に扱う | 親クラスの型で受け取る |
次回予告
基礎はバッチリですね!
でも、今回のコードをじっと見てみるといくつか気になる点が出てきませんか?
■タイプってどう表現する?
- ピカチュウはでんきタイプ
- ギャラドスはみず・ひこうタイプ
- リザードンはほのお・ひこうタイプ
継承で表現すると、ElectricPokemon?WaterFlyingPokemon?
タイプの組み合わせは100通り以上、全部クラスを作る?
■技の覚え方、これでいいの?
class Pikachu extends Pokemon {
thunderbolt(): void {
console.log("10まんボルト!");
}
}
今の設計だと、ピカチュウに「アイアンテール」を覚えさせたくなったら、Pikachuクラスを修正することになります。
でも実際のゲームでは、同じピカチュウでも個体によって覚えている技は違いますよね。
サトシのピカチュウと、あなたのピカチュウは、きっと違う技構成のはず。
これらの「違和感」の正体は、先人たちが発見したOOの原則にあります。
次回「OO原則編」では、タイプと技の設計を通じて、より柔軟な設計手法を学んでいきます。
次回学ぶこと
- 変化する部分をカプセル化する
- 継承よりコンポジションを好む
- インターフェースに対してプログラミングする
お楽しみに!
シリーズ目次
本記事は「ポケモンで学ぶオブジェクト指向」シリーズの一部です。