はじめに

「オブジェクト指向」は多くのJava学習者にとって最初の大きな壁のひとつです。
とくに「継承」「多態性(ポリモーフィズム)」「抽象化」のような用語が説明だけで終わると、実務への応用が難しくなります。

この記事では、誰でもイメージしやすい「ヒーロー」と「モンスター」の戦闘シーンをベースに、
オブジェクト指向の設計原則を構造とコードで理解できるよう解説します。

正しい設計から入る:多態性を活かした戦闘ロジック

まずは、構造と責務が適切に整理された例を提示します。
この例では、「戦える存在」をinterfaceで定義し、HeroMonsterはその能力を実装する形で構成されます。

戦える存在を表すインターフェース
// 「攻撃できる」という能力を抽象化
public interface Combatant {
    void attack(Combatant target); // 相手への攻撃
    String getName(); // 表示用の識別
}
ヒーローの基本実装
// Heroはプレイヤー側の戦闘キャラ
public class Hero implements Combatant {
    private final String name;
    public Hero(String name) {
        this.name = name;
    }
    @Override
    public void attack(Combatant target) {
        System.out.println(name + " strikes " + target.getName() + " with a sword!");
    }
    @Override
    public String getName() {
        return name;
    }
}
モンスターの基本実装
// Monsterは敵キャラとして攻撃ロジックを定義
public class Monster implements Combatant {
    private final String name;
    public Monster(String name) {
        this.name = name;
    }
    @Override
    public void attack(Combatant target) {
        System.out.println(name + " bites " + target.getName() + " viciously!");
    }
    @Override
    public String getName() {
        return name;
    }
}
ポリモーフィズムを活かした戦闘処理
// 戦闘ロジックがCombatant型のみで完結している
public class BattleSimulator {
    public void startBattle(Combatant a, Combatant b) {
        a.attack(b);
        b.attack(a);
    }
}
UML Diagram

この図のポイント:

  • Combatantが共通の行動(攻撃)を保証
  • HeroMonsterはそれぞれ異なる攻撃ロジックを持つが、同一のインターフェースで扱える
  • BattleSimulatorは中身を知らずに戦闘を指揮できる

オブジェクト指向の3原則をこの構造で見る

抽象化(Abstraction)

  • Combatantが「攻撃できるもの」という振る舞いだけを定義
  • 具体的な攻撃方法には立ち入らず、行動の本質だけを切り出している

カプセル化(Encapsulation)

  • nameは各クラス内で完結しており、外部からは取得のみ可能
  • 内部状態(例:攻撃方法)はクラス内で定義され、外部から変更されない

多態性(Polymorphism)

  • attack()の呼び出し先はインスタンスに応じて動的に決定
  • BattleSimulatorCombatantの型だけで振る舞いを完結

よくあるNG設計:共通化されていない戦闘処理

インターフェースを使わず、型を固定してしまう例
// プレイヤーとモンスターを別クラスで処理してしまう
public class BattleSimulator {
    public void startBattle(Hero hero, Monster monster) {
        hero.attack(monster);
        monster.attack(hero);
    }
}

継承やinterfaceを使わずに個別型をハードコーディングすると、拡張性が損なわれます。新しい敵キャラを追加するたびにBattleSimulatorを修正する羽目になります。

よくあるNG設計:役割をinterfaceに押し込めすぎる

NGな責務過多のinterface
// ロジックまでinterfaceに含めてしまっている例
public interface Combatant {
    void attack(Combatant target);
    default void heal() {
        System.out.println("Healing..."); // ← このような具象処理は適さない
    }
}

interfaceに実装責務(振る舞いの中身)を持たせ始めると、役割が曖昧になります。defaultメソッドの使いどころには注意が必要です。

レビューアー向けのチェックリスト

オブジェクト指向の設計レビュー観点

複数キャラクターが「攻撃できる」という共通性を持つなら、interfaceで抽象化してBattleSimulatorから分離できないか検討しましょう。

まとめ

「オブジェクト指向」の理解は、単なる言葉の暗記では不十分です。
大切なのは構造を通じて抽象化・再利用・拡張性を成立させる設計意図です。

この「ヒーローとモンスター」の例では:

  • interfaceは振る舞いの契約(攻撃)
  • 具象クラスは独自ロジックを持つ(剣 vs 噛みつき)
  • 利用側(BattleSimulator)は型に依存しない設計で再利用可能

正しく抽象化された構造は、読む側にも「なぜそう設計されたのか」が伝わるようになります。
レビューアーとしても、構造の読みやすさと意図の明瞭さを評価基準に持つことが重要です。