0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オブジェクト指向って何?RPGで理解する超入門 (抽象クラスとインターフェース)

0
Last updated at Posted at 2025-12-31

title_banner(1).png

所要時間

約25-30分(読むだけなら15分、コード実行とチェックまで含めると25-30分)
練習問題まで含めると、しっかり理解したい場合は30分程度を見ておくとよいです。

前提知識

この回を理解するには、以下の知識が必要です:

  • 第1回~第6回:オブジェクト指向の基礎
  • 第7回:継承の基礎(必須)
  • 第8回:ポリモーフィズム(必須)

前回までのおさらい記事一覧

1. なぜ抽象クラスとインターフェースが必要なのか?

よくある困りごと

前回(第8回)までで、継承とポリモーフィズムを学びました。しかし、実際にプログラムを作ると、こんな疑問が出てきます:

// 冒険者クラス(基本クラス)
public class Adventurer {
    protected String name;
    protected int hp;
    
    // この攻撃メソッド、何を書けばいいの...?
    public void attack() {
        // ???
        // 戦士は斬撃、魔法使いは魔法、それぞれ違うけど...
    }
}

問題点:

  • 親クラスのattack()メソッドの中身をどう書けばいいか分からない
  • 子クラスで必ずattack()を実装してほしいけど、強制できない
  • 「設計図だけ作って、詳細は子クラスに任せたい」という状況

解決策:抽象クラスとインターフェース

抽象クラスインターフェースを使えば:

  • 設計図(型枠)だけを作ることができる
  • 子クラスに実装を強制できる
  • 何を実装すべきか明確になる

例えるなら:

  • 抽象クラス = 「家の基本設計図」(基礎工事は済んでるけど、内装は自分で決めてね)
  • インターフェース = 「資格・免許」(この資格を持つなら、これができないとダメ)

abstract_concept.png

2. 抽象クラス(abstract class)とは

抽象クラスの特徴

抽象クラスは、「未完成のクラス」です
abstract「中身がまだ決まっていないクラス/メソッド」 です
「こういう動きは必要だけど、やり方は子クラスに任せる」
それを表すのがabstractです

Adventurer.java
// abstract をつけると抽象クラスになる
public abstract class Adventurer {
    // protectedは
    //「同じクラス」と「その子どもクラス(継承したクラス)」
    // から使えるアクセス修飾子
    protected String name;
    protected int hp;
    
    // 普通のメソッド(具体的な処理を書く)
    public void defend() {
        System.out.println(name + "は防御した!");
    }
    
    // 抽象メソッド(中身は書かない、子クラスで必ず実装)
    abstract public void attack();
    abstract public void useSkill();
}

重要なポイント

項目 説明
宣言方法 abstract class クラス名
抽象メソッド abstract をつけて、中身({})を書かない
インスタンス化 できないnew Adventurer() は不可)
継承 子クラスで extends して使う
普通のメソッド 具体的な処理も書ける
フィールド 変数も持てる

abstract class はインスタンス化(new)できません。
理由はとてもシンプルで、未完成のクラス だからです

実装例:戦士クラス

Warrior.java
// 抽象クラスを継承する子クラス
public class Warrior extends Adventurer {
    private int power;
    
    public Warrior(String name, int hp, int power) {
        this.name = name;
        this.hp = hp;
        this.power = power;
    }
    
    // 抽象メソッドを必ず実装する(@Overrideをつける)
    @Override
    public void attack() {
        System.out.println(name + "は剣で斬りつけた!");
        System.out.println("ダメージ: " + power);
    }
    
    @Override
    public void useSkill() {
        System.out.println(name + "は「剛力斬り」を使った!");
        System.out.println("ダメージ: " + (power * 2));
    }
}

理解ポイント

  • 抽象クラス = 「未完成の設計図」
  • 抽象メソッド = 「ここは子クラスで実装してね」という指示
  • 具体的なクラス(子クラス) = 抽象メソッドを全部実装して「完成版」にする

RPGで例えると:

  • 抽象クラス「冒険者」 = 「攻撃できる」「スキルを使える」という設計書だけ
  • 具体的なクラス「戦士」 = 「剣で斬る」「剛力斬り」と具体的に決める

abstract_class_diagram.png

3. インターフェース(interface)とは

インターフェースの特徴

インターフェース は、「クラスが守らなければならない約束事の一覧表 」 です。

// interface で宣言する

// 攻撃
public interface Attackable {
    // メソッドの宣言だけ(中身は書かない)
    public void attack();
}

// 回復
public interface Healable {
    public void heal(Adventurer target);
}

// 移動
public interface Movable {
    public void move(int x, int y);
}

重要なポイント

  • class ではなく interface
  • メソッドに中身 {} がない
  • 「必ず実装しなさい」という約束
項目 説明
宣言方法 interface インターフェース名
メソッド 宣言だけ(中身なし、abstract は省略可)
実装 クラスで implements を使う
多重実装 複数のインターフェースを同時に実装できる
インスタンス化 できない
継承 インターフェース同士で extends できる

実装例:白魔道士クラス

// 複数のインターフェースを実装できる
public class WhiteMage extends Adventurer implements Attackable, Healable {
    private int magic;
    private int healPower;
    
    public WhiteMage(String name, int hp, int magic, int healPower) {
        this.name = name;
        this.hp = hp;
        this.magic = magic;
        this.healPower = healPower;
    }
    
    // Adventurer の抽象メソッド実装
    @Override
    public void attack() {
        System.out.println(name + "は杖で攻撃した!");
        System.out.println("ダメージ: " + magic);
    }
    
    @Override
    public void useSkill() {
        System.out.println(name + "は「全体回復」を使った!");
    }
    
    // Attackable インターフェースの実装
    // (attack()はすでに実装済み)
    
    // Healable インターフェースの実装
    @Override
    public void heal(Adventurer target) {
        System.out.println(name + "は" + target.name + "を回復した!");
        target.hp += healPower;
        System.out.println(target.name + "のHP: " + target.hp);
    }
}

理解ポイント

  • インターフェース = 「資格・免許」
  • implements(実装) = 「この資格を取得します」という宣言
  • 複数実装 = 「複数の資格を同時に持てる」

RPGで例えると

  • Attackable(攻撃可能) = 「攻撃できる資格」
  • Healable(回復可能) = 「回復できる資格」
  • 白魔道士 = 「攻撃もできるし、回復もできる」(両方の資格を持つ)

4. 抽象クラスとインターフェースの違い

比較表

abstract_vs_interface.png

項目 抽象クラス インターフェース
キーワード abstract class interface
継承/実装 extends(単一継承) implements(多重実装可)
メソッド 具体的なメソッドも書ける 宣言のみ(Java 8以降はdefaultメソッド可)
フィールド 持てる △ 定数のみ(public static final
コンストラクタ 持てる 持てない
用途 「共通の基本機能 + 未完成部分」 「できることの約束」
例え 家の基本設計図 資格・免許証

どちらを使うべき?

【抽象クラスを使う場合】
共通のフィールド(変数)が必要
共通の具体的なメソッドがある
「is-a」の関係(〜は〜である)
例:戦士 is-a 冒険者

【インターフェースを使う場合】
複数の「できること」を組み合わせたい
異なる系統のクラスに同じ能力を与えたい
「can-do」の関係(〜は〜できる)
例:白魔道士 can 回復する、ドラゴン can 飛ぶ

実際の使い分け

  • 抽象クラス:共通の基本機能
Adventurer.java
public abstract class Adventurer {
    protected String name;  // 共通フィールド
    protected int hp;
    
    public void defend() {  // 共通メソッド
        System.out.println(name + "は防御した!");
    }
    
    abstract public void attack();  // 子クラスで実装
}
  • インターフェース:できることの約束
Flyable.java
public interface Flyable {
    void fly();  // 飛べる能力
}
Breathable.java
public interface Breathable {
    void breathFire();  // 火を吹ける能力
}
  • ドラゴンクラス
Dragon.java
// ドラゴン:冒険者ではないけど、飛べるし火を吹ける
public class Dragon implements Flyable, Breathable {
    @Override
    public void fly() {
        System.out.println("ドラゴンが空を飛んだ!");
    }
    
    @Override
    public void breathFire() {
        System.out.println("ドラゴンが火を吹いた!");
    }
}
  • テストクラス
DragonTest.java
public class DragonTest {
	public static void main(String[] args) {
		Dragon dragon =  new Dragon("ドラゴン",100,200);
		dragon.breathFire(); // 火を吹く
		dragon.fly();        // 空を飛ぶ
	}
}

実行結果

ドラゴンが火を吹いた!
ドラゴンが空を飛んだ!

5. 実践:RPGキャラクターの能力設計

実践例

character_ability_design.png

設計方針

RPGには様々なキャラクターがいます:

  • 戦士:攻撃できる、防御できる
  • 魔法使い:攻撃できる、魔法を使える
  • 白魔道士:攻撃できる、回復できる
  • ドラゴン:攻撃できる、飛べる、火を吹ける

これらを設計するには?

設計図

【抽象クラス】
Adventurer(冒険者)
└─ 共通機能:name, hp, defend()
└─ 抽象メソッド:attack(), useSkill()

【インターフェース】
- Attackable(攻撃可能)
- Healable(回復可能)
- Flyable(飛行可能)
- MagicUser(魔法使用可能)

完全実装例

5.1 抽象クラスとインターフェースの定義

Adventurer.java
// ========================================
// 抽象クラス:冒険者の基本設計
// ========================================
public abstract class Adventurer {
    protected String name;
    protected int hp;
    protected int level;
    
    // コンストラクタ
    public Adventurer(String name, int hp) {
        this.name = name;
        this.hp = hp;
        this.level = 1;
    }
    
    // 具体的なメソッド(共通機能)
    public void defend() {
        System.out.println(name + "は防御の構えをとった!");
    }
    
    public void showStatus() {
        System.out.println("━━━━━━━━━━━━━━");
        System.out.println("名前: " + name);
        System.out.println("HP: " + hp);
        System.out.println("レベル: " + level);
        System.out.println("━━━━━━━━━━━━━━");
    }
    
    // 抽象メソッド(子クラスで必ず実装)
    abstract public void attack();
    abstract public void useSkill();
}

インターフェース:能力の定義

Attackable.java
public interface Attackable {
    void attack();
}
Healable.java
public interface Healable {
    void heal(Adventurer target);
    void healAll(Adventurer[] party);
}
MagicUser.java
public interface MagicUser {
    void castMagic(String spellName);
}
Flyable.java
public interface Flyable {
    void fly();
    void land();
}

5.2 具体的なクラスの実装

戦士クラス

Warrior.java
public class Warrior extends Adventurer implements Attackable {
    private int power;
    private boolean isGuarding;
    
    public Warrior(String name, int hp, int power) {
        super(name, hp);
        this.power = power;
        this.isGuarding = false;
    }
    
    @Override
    public void attack() {
        System.out.println(name + "は剣で斬りつけた!");
        System.out.println("ダメージ: " + power);
    }
    
    @Override
    public void useSkill() {
        System.out.println(name + "は「剛力斬り」を使った!");
        System.out.println("ダメージ: " + (power * 2) + "(2倍攻撃)");
    }
    
    public void guardStance() {
        isGuarding = true;
        System.out.println(name + "は守りの構えをとった!");
    }
}

白魔道士クラス

WhiteMage.java
public class WhiteMage extends Adventurer implements Attackable, Healable, MagicUser {
    private int magic;
    private int healPower;
    private int mp;
    
    public WhiteMage(String name, int hp, int magic, int healPower) {
        super(name, hp);
        this.magic = magic;
        this.healPower = healPower;
        this.mp = 50;
    }
    
    @Override
    public void attack() {
        System.out.println(name + "は杖で攻撃した!");
        System.out.println("ダメージ: " + magic);
    }
    
    @Override
    public void useSkill() {
        System.out.println(name + "は「全体回復」を使った!");
        System.out.println("全員のHPが回復した!");
    }
    
    @Override
    public void heal(Adventurer target) {
        if (mp >= 10) {
            System.out.println(name + "は" + target.name + "を回復した!");
            target.hp += healPower;
            mp -= 10;
            System.out.println(target.name + "のHP: " + target.hp);
            System.out.println("(残りMP: " + mp + ")");
        } else {
            System.out.println(name + "のMPが足りない!");
        }
    }
    
    @Override
    public void healAll(Adventurer[] party) {
        if (mp >= 30) {
            System.out.println(name + "は全体回復魔法を唱えた!");
            for (Adventurer member : party) {
                member.hp += healPower / 2;
                System.out.println(member.name + "のHP: " + member.hp);
            }
            mp -= 30;
            System.out.println("(残りMP: " + mp + ")");
        } else {
            System.out.println(name + "のMPが足りない!");
        }
    }
    
    @Override
    public void castMagic(String spellName) {
        System.out.println(name + "は「" + spellName + "」を唱えた!");
        System.out.println("魔法が発動した!");
    }
}

黒魔道士クラス

BlackMage.java
public class BlackMage extends Adventurer implements Attackable, MagicUser {
    private int magic;
    private int mp;
    
    public BlackMage(String name, int hp, int magic) {
        super(name, hp);
        this.magic = magic;
        this.mp = 50;
    }
    
    @Override
    public void attack() {
        System.out.println(name + "は杖で攻撃した!");
        System.out.println("ダメージ: " + magic);
    }
    
    @Override
    public void useSkill() {
        System.out.println(name + "は「メテオ」を使った!");
        System.out.println("超ダメージ: " + (magic * 3));
    }
    
    @Override
    public void castMagic(String spellName) {
        if (mp >= 15) {
            System.out.println(name + "は「" + spellName + "」を唱えた!");
            System.out.println("魔法ダメージ: " + (magic * 2));
            mp -= 15;
            System.out.println("(残りMP: " + mp + ")");
        } else {
            System.out.println(name + "のMPが足りない!");
        }
    }
}

ドラゴンクラス(冒険者ではないモンスター)

Dragon.java
public class Dragon implements Attackable, Flyable {
    private String name;
    private int hp;
    private int power;
    private boolean isFlying;
    
    public Dragon(String name, int hp, int power) {
        this.name = name;
        this.hp = hp;
        this.power = power;
        this.isFlying = false;
    }
    
    @Override
    public void attack() {
        System.out.println(name + "は爪で攻撃した!");
        System.out.println("ダメージ: " + power);
    }
    
    public void breathFire() {
        System.out.println(name + "は炎を吹いた!");
        System.out.println("ダメージ: " + (power * 2));
    }
    
    @Override
    public void fly() {
        if (!isFlying) {
            isFlying = true;
            System.out.println(name + "は空に飛び立った!");
        } else {
            System.out.println(name + "はすでに飛行中だ!");
        }
    }
    
    @Override
    public void land() {
        if (isFlying) {
            isFlying = false;
            System.out.println(name + "は地上に降り立った!");
        } else {
            System.out.println(name + "はすでに地上にいる!");
        }
    }
}

5.3 テストプログラム

AbstractInterfaceDemo.java
public class AbstractInterfaceDemo {
    public static void main(String[] args) {
        System.out.println("===========================================");
        System.out.println("   RPGキャラクター能力デモ");
        System.out.println("===========================================\n");
        
        // キャラクター生成
        Warrior warrior = new Warrior("アレン", 150, 30);
        WhiteMage whiteMage = new WhiteMage("リリア", 100, 15, 25);
        BlackMage blackMage = new BlackMage("ゼノ", 80, 35);
        Dragon dragon = new Dragon("レッドドラゴン", 300, 50);
        
        // ▼▼▼ 戦士の行動 ▼▼▼
        System.out.println("【戦士の行動】");
        warrior.showStatus();
        warrior.attack();
        warrior.useSkill();
        warrior.defend();
        System.out.println();
        
        // ▼▼▼ 白魔道士の行動 ▼▼▼
        System.out.println("【白魔道士の行動】");
        whiteMage.showStatus();
        whiteMage.attack();
        whiteMage.castMagic("ホーリー");
        whiteMage.heal(warrior);  // 戦士を回復
        System.out.println();
        
        // ▼▼▼ 黒魔道士の行動 ▼▼▼
        System.out.println("【黒魔道士の行動】");
        blackMage.showStatus();
        blackMage.attack();
        blackMage.castMagic("ファイガ");
        blackMage.useSkill();
        System.out.println();
        
        // ▼▼▼ パーティ全体回復 ▼▼▼
        System.out.println("【パーティ全体回復】");
        Adventurer[] party = {warrior, whiteMage, blackMage};
        whiteMage.healAll(party);
        System.out.println();
        
        // ▼▼▼ ドラゴンの行動 ▼▼▼
        System.out.println("【ドラゴンの行動】");
        dragon.attack();
        dragon.fly();
        dragon.breathFire();
        dragon.land();
        System.out.println();
        
        // ▼▼▼ ポリモーフィズムの活用 ▼▼▼
        System.out.println("【ポリモーフィズムで統一管理】");
        Attackable[] attackers = {warrior, whiteMage, blackMage, dragon};
        for (Attackable attacker : attackers) {
            attacker.attack();
        }
        System.out.println();
        
        System.out.println("===========================================");
        System.out.println("   デモ終了");
        System.out.println("===========================================");
    }
}

実行結果

===========================================
   RPGキャラクター能力デモ
===========================================

【戦士の行動】
━━━━━━━━━━━━━━
名前: アレン
HP: 150
レベル: 1
━━━━━━━━━━━━━━
アレンは剣で斬りつけた!
ダメージ: 30
アレンは「剛力斬り」を使った!
ダメージ: 60(2倍攻撃)
アレンは防御の構えをとった!

【白魔道士の行動】
━━━━━━━━━━━━━━
名前: リリア
HP: 100
レベル: 1
━━━━━━━━━━━━━━
リリアは杖で攻撃した!
ダメージ: 15
リリアは「ホーリー」を唱えた!
魔法が発動した!
リリアはアレンを回復した!
アレンのHP: 175
(残りMP: 40)

【黒魔道士の行動】
━━━━━━━━━━━━━━
名前: ゼノ
HP: 80
レベル: 1
━━━━━━━━━━━━━━
ゼノは杖で攻撃した!
ダメージ: 35
ゼノは「ファイガ」を唱えた!
魔法ダメージ: 70
(残りMP: 35)
ゼノは「メテオ」を使った!
超ダメージ: 105

【パーティ全体回復】
リリアは全体回復魔法を唱えた!
アレンのHP: 187
リリアのHP: 112
ゼノのHP: 92
(残りMP: 10)

【ドラゴンの行動】
レッドドラゴンは爪で攻撃した!
ダメージ: 50
レッドドラゴンは空に飛び立った!
レッドドラゴンは炎を吹いた!
ダメージ: 100
レッドドラゴンは地上に降り立った!

【ポリモーフィズムで統一管理】
アレンは剣で斬りつけた!
ダメージ: 30
リリアは杖で攻撃した!
ダメージ: 15
ゼノは杖で攻撃した!
ダメージ: 35
レッドドラゴンは爪で攻撃した!
ダメージ: 50

===========================================
   デモ終了
===========================================

practice_problems_banner.png

練習問題1:基本的な抽象クラス

問題:
以下の要件を満たす抽象クラスWeapon(武器)と、それを継承したSword(剣)クラスを作成してください。

要件:

  • Weaponクラス(武器)
    フィールド:name(武器名)、power(攻撃力)
    具体的メソッド:showInfo()(武器情報を表示)
    抽象メソッド:attack()(攻撃方法は武器によって異なる)

  • Swordクラス (剣)
    Weaponを継承
    attack()を実装(「剣で斬りつけた!」と表示)

解答を見る

抽象クラス:武器

Weapon.java
public abstract class Weapon {
    protected String name;
    protected int power;
    
    public Weapon(String name, int power) {
        this.name = name;
        this.power = power;
    }
    
    // 具体的メソッド
    public void showInfo() {
        System.out.println("━━━━━━━━━━━━━━");
        System.out.println("武器名: " + name);
        System.out.println("攻撃力: " + power);
        System.out.println("━━━━━━━━━━━━━━");
    }
    
    // 抽象メソッド
    abstract public void attack();
}

具体的クラス:剣

Sword.java
public class Sword extends Weapon {
    public Sword(String name, int power) {
        super(name, power);
    }
    
    @Override
    public void attack() {
        System.out.println(name + "で斬りつけた!");
        System.out.println("ダメージ: " + power);
    }
}

テストクラス

WeaponTest.java
public class WeaponTest {
    public static void main(String[] args) {
        Sword sword = new Sword("エクスカリバー", 50);
        sword.showInfo();
        sword.attack();
    }
}

実行結果

━━━━━━━━━━━━━━
武器名: エクスカリバー
攻撃力: 50
━━━━━━━━━━━━━━
エクスカリバーで斬りつけた!
ダメージ: 50

解説:

  • abstract class Weaponで抽象クラスを宣言
  • abstract public void attack()で抽象メソッドを宣言(中身なし)
  • Swordクラスで@Overrideをつけてattack()を実装
  • 抽象クラスはnew Weapon()できないが、new Sword()はできる

練習問題2:インターフェースの実装

問題:
以下の要件を満たすインターフェースSwimmable(泳げる)と、それを実装したFish(魚)クラスを作成してください。

要件:

  • Swimmableインターフェース
    メソッド:swim()(泳ぐ動作)

  • Fishクラス
    フィールド:name(名前)
    Swimmableを実装
    swim()を実装(「〇〇は泳いだ!」と表示)

解答を見る

インターフェース:泳げる

Swimmable.java
public interface Swimmable {
    void swim();
}

具体的クラス:魚

Fish.java
public class Fish implements Swimmable {
    private String name;
    
    public Fish(String name) {
        this.name = name;
    }
    
    @Override
    public void swim() {
        System.out.println(name + "は泳いだ!");
        System.out.println("🐟💨 スイスイ〜");
    }
}

テストクラス

SwimmableTest.java
public class SwimmableTest {
    public static void main(String[] args) {
        Fish fish = new Fish("マグロ");
        fish.swim();
        
        // ポリモーフィズムで扱える
        Swimmable swimmer = new Fish("サーモン");
        swimmer.swim();
    }
}

実行結果:

マグロは泳いだ!
🐟💨 スイスイ〜
サーモンは泳いだ!
🐟💨 スイスイ〜

解説:

  • interface Swimmableでインターフェースを宣言
  • class Fish implements Swimmableで実装を宣言
  • @Overrideをつけてswim()を実装
  • インターフェース型の変数(Swimmable swimmer)で扱える(ポリモーフィズム)

練習問題3:複数インターフェースの実装

問題:
以下の要件を満たすDuck(アヒル)クラスを作成してください。

要件:

  • インターフェース:Swimmable(泳げる)、Flyable(飛べる)を両方実装
  • フィールド:name(名前)
  • メソッド:swim()fly()を実装
解答を見る

インターフェース:泳げる

Swimmable.java
public interface Swimmable {
    void swim();
}

インターフェース:飛べる

Flyable.java
public interface Flyable {
    void fly();
    void land();
}

具体的クラス:アヒル(泳げるし飛べる)

Duck.java
public class Duck implements Swimmable, Flyable {
    private String name;
    private boolean isFlying;
    
    public Duck(String name) {
        this.name = name;
        this.isFlying = false;
    }
    
    @Override
    public void swim() {
        if (!isFlying) {
            System.out.println(name + "は水面を泳いだ!");
            System.out.println("🦆💨 スイスイ〜");
        } else {
            System.out.println(name + "は飛行中なので泳げない!");
        }
    }
    
    @Override
    public void fly() {
        if (!isFlying) {
            isFlying = true;
            System.out.println(name + "は空を飛んだ!");
            System.out.println("🦆✨ バサバサ〜");
        } else {
            System.out.println(name + "はすでに飛行中だ!");
        }
    }
    
    @Override
    public void land() {
        if (isFlying) {
            isFlying = false;
            System.out.println(name + "は着地した!");
        } else {
            System.out.println(name + "はすでに地上にいる!");
        }
    }
}

テストクラス

DuckTest.java
public class DuckTest {
    public static void main(String[] args) {
        Duck duck = new Duck("ドナルド");
        
        duck.swim();   // 泳ぐ
        duck.fly();    // 飛ぶ
        duck.swim();   // 飛行中は泳げない
        duck.land();   // 着地
        duck.swim();   // 再び泳げる
    }
}

実行結果:

ドナルドは水面を泳いだ!
🦆💨 スイスイ〜
ドナルドは空を飛んだ!
🦆✨ バサバサ〜
ドナルドは飛行中なので泳げない!
ドナルドは着地した!
ドナルドは水面を泳いだ!
🦆💨 スイスイ〜

解説:

  • implements Swimmable, Flyableで複数のインターフェースを実装
  • カンマ(,)で区切って複数指定できる
  • すべてのインターフェースのメソッドを実装する必要がある
  • 状態(isFlying)を持たせて、飛行中は泳げないようにする

7. 応用問題

応用問題:RPGアイテムシステムの設計

問題:
RPGのアイテムシステムを設計してください。

要件:

1.抽象クラスItem(アイテム)

  • フィールド:name(アイテム名)、price(価格)
  • 具体的メソッド:showInfo()(アイテム情報表示)
  • 抽象メソッド:use(Adventurer target)(使用効果は子クラスで実装)

2.インターフェース(3ファイル)

  • Usableuse(Adventurer target)(使用できる)
  • Sellablesell()(売却できる)
  • ThrowablethrowAway()(捨てられる)

3.具体的なアイテムクラス(3ファイル)

  • Potion(ポーション):HP回復アイテム
  • Elixir(エリクサー):完全回復アイテム
  • MagicStone(魔石):売却専用アイテム

実装してほしい機能:

  • ポーション:HPを50回復
  • エリクサー:HPを完全回復
  • 魔石:売却すると1000ゴールド

冒険者クラス(簡易版):

Adventurer.java
public class Adventurer {
    String name;
    int hp;
    
    public Adventurer(String name, int hp) {
        this.name = name;
        this.hp = hp;
    }
}
解答を見る

抽象クラス:アイテム

Item.java
public abstract class Item {
    protected String name;
    protected int price;
    
    public Item(String name, int price) {
        this.name = name;
        this.price = price;
    }
    
    public void showInfo() {
        System.out.println("━━━━━━━━━━━━━━");
        System.out.println("アイテム: " + name);
        System.out.println("価格: " + price + "G");
        System.out.println("━━━━━━━━━━━━━━");
    }
    
    abstract public void use(Adventurer target);
}

インターフェース

Usable.java
public interface Usable {
    void use(Adventurer target);
}
Sellable.java
public interface Sellable {
    void sell();
}
Throwable.java
public interface Throwable {
    void throwAway();
}

具体的なアイテムクラス
ポーション(使用可能、売却可能、捨てられる)

Potion.java
public class Potion extends Item implements Usable, Sellable, Throwable {
    private int healAmount;
    
    public Potion() {
        super("ポーション", 50);
        this.healAmount = 50;
    }
    
    @Override
    public void use(Adventurer target) {
        System.out.println(target.name + "は" + name + "を使った!");
        target.hp += healAmount;
        System.out.println("HPが" + healAmount + "回復した!");
        System.out.println("   現在のHP: " + target.hp);
    }
    
    @Override
    public void sell() {
        System.out.println(name + "を売却した!");
        System.out.println((price / 2) + "ゴールドを手に入れた!");
    }
    
    @Override
    public void throwAway() {
        System.out.println(name + "を捨てた...");
    }
}

エリクサー(使用可能、売却可能、捨てられる)

Elixir.java
public class Elixir extends Item implements Usable, Sellable, Throwable {
    public Elixir() {
        super("エリクサー", 500);
    }
    
    @Override
    public void use(Adventurer target) {
        System.out.println(target.name + "は" + name + "を使った!");
        int maxHp = 200;  // 仮の最大HP
        target.hp = maxHp;
        System.out.println("HPが完全回復した!");
        System.out.println("   現在のHP: " + target.hp);
    }
    
    @Override
    public void sell() {
        System.out.println(name + "を売却した!");
        System.out.println((price / 2) + "ゴールドを手に入れた!");
    }
    
    @Override
    public void throwAway() {
        System.out.println(name + "を捨てた...(もったいない!)");
    }
}

魔石(売却専用、捨てられる)

MagicStone.java
public class MagicStone extends Item implements Sellable, Throwable {
    public MagicStone() {
        super("魔石", 1000);
    }
    
    @Override
    public void use(Adventurer target) {
        System.out.println(name + "は使用できないアイテムです!");
    }
    
    @Override
    public void sell() {
        System.out.println(name + "を売却した!");
        System.out.println(price + "ゴールドを手に入れた!");
    }
    
    @Override
    public void throwAway() {
        System.out.println(name + "を捨てた...(もったいない!!!)");
    }
}

テストプログラム

ItemSystemDemo.java
public class ItemSystemDemo {
    public static void main(String[] args) {
        System.out.println("===================================");
        System.out.println("   RPGアイテムシステム");
        System.out.println("===================================\n");
        
        // 冒険者作成
        Adventurer hero = new Adventurer("勇者", 80);
        System.out.println("勇者のHP: " + hero.hp + "\n");
        
        // アイテム作成
        Potion potion = new Potion();
        Elixir elixir = new Elixir();
        MagicStone stone = new MagicStone();
        
        // ▼▼▼ ポーションの使用 ▼▼▼
        System.out.println("【ポーションのテスト】");
        potion.showInfo();
        potion.use(hero);
        System.out.println();
        
        // ▼▼▼ エリクサーの使用 ▼▼▼
        System.out.println("【エリクサーのテスト】");
        elixir.showInfo();
        elixir.use(hero);
        System.out.println();
        
        // ▼▼▼ 売却テスト ▼▼▼
        System.out.println("【売却テスト】");
        potion.sell();
        elixir.sell();
        stone.sell();
        System.out.println();
        
        // ▼▼▼ 捨てるテスト ▼▼▼
        System.out.println("【捨てるテスト】");
        potion.throwAway();
        elixir.throwAway();
        stone.throwAway();
        System.out.println();
        
        // ▼▼▼ ポリモーフィズムでの管理 ▼▼▼
        System.out.println("【ポリモーフィズムでの統一管理】");
        Usable[] usableItems = {new Potion(), new Elixir()};
        for (Usable item : usableItems) {
            item.use(hero);
        }
        System.out.println();
        
        System.out.println("===================================");
        System.out.println("   デモ終了");
        System.out.println("===================================");
    }
}

実行結果

===================================
   RPGアイテムシステム
===================================

勇者のHP: 80

【ポーションのテスト】
━━━━━━━━━━━━━━
アイテム: ポーション
価格: 50G
━━━━━━━━━━━━━━
勇者はポーションを使った!
HPが50回復した!
   現在のHP: 130

【エリクサーのテスト】
━━━━━━━━━━━━━━
アイテム: エリクサー
価格: 500G
━━━━━━━━━━━━━━
勇者はエリクサーを使った!
HPが完全回復した!
   現在のHP: 200

【売却テスト】
ポーションを売却した!
25ゴールドを手に入れた!
エリクサーを売却した!
250ゴールドを手に入れた!
魔石を売却した!
1000ゴールドを手に入れた!

【捨てるテスト】
ポーションを捨てた...
エリクサーを捨てた...(もったいない!)
魔石を捨てた...(もったいない!!!)

【ポリモーフィズムでの統一管理】
勇者はポーションを使った!
HPが50回復した!
   現在のHP: 250
勇者はエリクサーを使った!
HPが完全回復した!
   現在のHP: 200

===================================
   デモ終了
===================================

解説:

1.抽象クラスItem

  • 共通フィールド(name, price)を持つ
  • 共通メソッド(showInfo())を実装
  • 抽象メソッド(use())は子クラスで実装

2.インターフェース設計

  • Usable:使用できるアイテム
  • Sellable:売却できるアイテム
  • Throwable:捨てられるアイテム

3.複数インターフェース実装

  • PotionElixirは3つすべてのインターフェースを実装
  • MagicStoneUsableを実装せず、売却専用
  1. ポリモーフィズムの活用
    • Usable[]配列で使用可能アイテムを統一管理
    • インターフェース型で扱うことで柔軟な設計

summary_banner.png

この回で学んだこと

概念 説明 キーワード
抽象クラス 未完成のクラス、共通機能 + 抽象メソッド abstract class, abstract
抽象メソッド 実装を子クラスに強制する 中身なし、子で@Override
インターフェース できることの約束、複数実装可能 interface, implements
設計の型枠 何を実装すべきか明確にする 設計の統一、保守性向上

わかりやすくいうと

  • 抽象クラス = 「未完成の設計図」(家の基本構造は決まってるけど、内装は自由)
  • インターフェース = 「資格・免許」(これができないとダメ、という約束)
  • 複数実装 = 「複数の資格を同時に持てる」
  • 設計の型枠 = 「何を作るべきか迷わない」

使い分けのポイント

【抽象クラス】
共通のフィールド・メソッドがある
「〜は〜である」の関係
単一継承のみ
例:戦士 is-a 冒険者

【インターフェース】
「〜は〜できる」の能力定義
複数の能力を組み合わせたい
多重実装可能
例:白魔道士 can 回復する、攻撃する

実務での重要性

1.設計の明確化

  • 何を実装すべきか一目瞭然
  • チーム開発で役割分担しやすい

2.保守性の向上

  • 新しい機能追加が容易
  • 既存コードへの影響を最小化

3.ポリモーフィズムの活用

  • インターフェース型で統一管理
  • 柔軟で拡張性の高い設計

summary.png

補足:Java 8以降のインターフェース

🆕 defaultメソッドとstaticメソッド

Java 8以降、インターフェースにも具体的なメソッドを書けるようになりました。

インターフェース

Attackable.java
public interface Attackable {
    // 通常の抽象メソッド
    void attack();
    
    // defaultメソッド(デフォルト実装を提供)
    default void criticalAttack() {
        System.out.println("クリティカルヒット!");
        attack();  // 抽象メソッドを呼び出せる
        System.out.println("ダメージ2倍!");
    }
    
    // staticメソッド(ユーティリティメソッド)
    static void showAttackTips() {
        System.out.println("攻撃のコツ:敵の弱点を狙おう!");
    }
}

実装クラス

Warrior.java
public class Warrior implements Attackable {
    @Override
    public void attack() {
        System.out.println("剣で斬りつけた!");
    }
    
    // criticalAttack()は継承されるので実装不要
    // でも、必要ならオーバーライドもできる
}

テストクラス

AttackableTest.java
public class AttackableTest {
	public static void main(String[] args) {
		Warrior warrior = new Warrior();
		warrior.attack();             // 通常攻撃
		warrior.criticalAttack();     // デフォルト実装を使用
		Attackable.showAttackTips();  // staticメソッド呼び出し
	}
}

実行結果

剣で斬りつけた!
クリティカルヒット!
剣で斬りつけた!
ダメージ2倍!
攻撃のコツ:敵の弱点を狙おう!

defaultメソッドの使い道

1.後方互換性

  • 既存のインターフェースに新メソッドを追加しても、既存の実装クラスが壊れない

2.共通処理の提供

  • よく使う処理をデフォルト実装として提供
  • 必要なら個別にオーバーライド可能

3.実装の簡素化

  • すべてのメソッドを実装する負担を軽減

補足:実務でよく使うデザインパターン

テンプレートメソッドパターン

抽象クラスを使った代表的な設計パターンです。

BattleTemplate.java
// 抽象クラス:バトルの流れを定義
public abstract class BattleTemplate {
    // テンプレートメソッド(流れを定義)
    public final void executeBattle() {
        enterBattle();
        attack();
        useSkill();
        exitBattle();
    }
    
    // 共通処理
    private void enterBattle() {
        System.out.println("━━━━━━━━━━━━━━");
        System.out.println("   バトル開始!");
        System.out.println("━━━━━━━━━━━━━━");
    }
    
    private void exitBattle() {
        System.out.println("━━━━━━━━━━━━━━");
        System.out.println("   バトル終了!");
        System.out.println("━━━━━━━━━━━━━━");
    }
    
    // 抽象メソッド(子クラスで実装)
    abstract protected void attack();
    abstract protected void useSkill();
}

具体的な実装

WarriorBattle.java
public class WarriorBattle extends BattleTemplate {
    @Override
    protected void attack() {
        System.out.println("剣で攻撃!");
    }
    
    @Override
    protected void useSkill() {
        System.out.println("剛力斬り発動!");
    }
}
MageBattle.java
public class MageBattle extends BattleTemplate {
    @Override
    protected void attack() {
        System.out.println("魔法攻撃!");
    }
    
    @Override
    protected void useSkill() {
        System.out.println("サンダーストーム発動!");
    }
}

使用例

TemplatePatternDemo.java
public class TemplatePatternDemo {
    public static void main(String[] args) {
        BattleTemplate warrior = new WarriorBattle();
        warrior.executeBattle();
        
        System.out.println();
        
        BattleTemplate mage = new MageBattle();
        mage.executeBattle();
    }
}

実行結果

━━━━━━━━━━━━━━
   バトル開始!
━━━━━━━━━━━━━━
剣で攻撃!
剛力斬り発動!
━━━━━━━━━━━━━━
   バトル終了!
━━━━━━━━━━━━━━

━━━━━━━━━━━━━━
   バトル開始!
━━━━━━━━━━━━━━
魔法攻撃!
サンダーストーム発動!
━━━━━━━━━━━━━━
   バトル終了!
━━━━━━━━━━━━━━

テンプレートメソッドパターンの利点

1.処理の流れを統一

  • バトル開始→攻撃→スキル→バトル終了の順序を固定

2.共通処理を一箇所に

  • enterBattle()exitBattle()は全職業共通

3.拡張が容易

  • 新しい職業(弓使い、格闘家など)を追加しやすい

補足:よくある質問(FAQ)

Q1: 抽象クラスとインターフェース、どっちを使えばいい?

A: 迷ったら以下の基準で判断してください:

【抽象クラスを選ぶ場合】
共通のフィールド(変数)が必要
共通の具体的メソッドがある
「is-a」の関係(戦士は冒険者である)

【インターフェースを選ぶ場合】
複数の能力を組み合わせたい
「can-do」の関係(アヒルは泳げる、飛べる)
異なる系統のクラスに同じ能力を与えたい

Q2: インターフェースに変数は書けないの?

A: 書けますが、自動的にpublic static final(定数)になります

public interface GameConfig {
    // 自動的に public static final になる
    int MAX_LEVEL = 99;
    int MAX_HP = 9999;
    String GAME_TITLE = "Java Quest";
}

// 使用例
public class GameConfigDemo {
	public static void main(String[] args) {
		System.out.println(GameConfig.GAME_TITLE); // "Java Quest"
		// GameConfig.MAX_LEVEL = 100;  // エラー!定数なので変更不可
	}
}

Q3: 抽象クラスを継承したけど、抽象メソッドを実装しなかったらどうなる?

A: その子クラスも抽象クラスにしないとエラーになります

public abstract class Adventurer {
    abstract public void attack();
}

// ケース1:抽象メソッドを実装しない → エラー
public class Warrior extends Adventurer {
    // attack()を実装していない → コンパイルエラー!
}

// ケース2:子クラスも抽象クラスにする → OK
public abstract class Warrior extends Adventurer {
    // attack()を実装しなくてもOK(この子クラスも抽象クラス)
}

// ケース3:抽象メソッドを実装する → OK
public class Warrior extends Adventurer {
    @Override
    public void attack() {
        System.out.println("攻撃!");
    }
}

Q4: インターフェースを複数実装する順番は関係ある?

A: 順番は関係ありません。どの順番でも同じです。

// これらはすべて同じ意味
class WhiteMage implements Attackable, Healable, MagicUser { }
class WhiteMage implements Healable, Attackable, MagicUser { }
class WhiteMage implements MagicUser, Healable, Attackable { }

Q5: 抽象クラスのコンストラクタは呼び出せる?

A: newで直接呼び出すことはできませんが、子クラスからsuper()で呼び出せます

abstract class Adventurer {
    protected String name;
    
    // 抽象クラスもコンストラクタを持てる
    public Adventurer(String name) {
        this.name = name;
        System.out.println("冒険者 " + name + " を作成中...");
    }
}

class Warrior extends Adventurer {
    public Warrior(String name) {
        super(name);  // 親クラスのコンストラクタを呼び出す
        System.out.println("戦士として初期化完了!");
    }
}

// 使用例
// Adventurer adv = new Adventurer("テスト");  // エラー!
Warrior warrior = new Warrior("アレン");  // OK!

実行結果

冒険者 アレン を作成中...
戦士として初期化完了!

Q6: 修飾子の扱いと構文がわかりません

A: 修飾子の扱いまとめ表(static 含む)

書き方 結果 備考
public abstract void ⭕ OK 順不同
abstract public void ⭕ OK 順不同
public default void ⭕ OK interface のみ
default public void ❌ NG 文法エラー
default void ⭕ OK public 省略
static void ⭕ OK クラス/インターフェース両方可
public static void ⭕ OK 順不同
static public void ⭕ OK 順不同
abstract static void ❌ NG 一緒に使えない
default static void ❌ NG 同時指定不可
static abstract void ❌ NG static は実装必須

next_episode_preview_banner-1.png

第10回:総合演習 - RPGバトルシステム構築

これまで学んだすべての知識を使って、本格的なRPGバトルシステムを作ります!

  • パーティ編成
  • ターン制バトル
  • スキルシステム
  • 装備システム
  • 経験値・レベルアップ

お楽しみに!


reference_materials_banner-1.png


series_list_banner-1.png

  1. 第1回:オブジェクト指向って何?RPGで理解する超入門
  2. 第2回:オブジェクト指向って何?RPGで理解する超入門(配列とループ)
  3. 第3回:オブジェクト指向って何?RPGで理解する超入門(クラス設計の基礎)
  4. 第4回:オブジェクト指向って何?RPGで理解する超入門(コンストラクタ)
  5. 第5回:オブジェクト指向って何?RPGで理解する超入門(カプセル化)
  6. 第6回:オブジェクト指向って何?RPGで理解する超入門(参照型を深く理解する)
  7. 第7回:オブジェクト指向って何?RPGで理解する超入門(継承の基礎)
  8. 第8回:オブジェクト指向って何?RPGで理解する超入門(ポリモーフィズム)
  9. 第9回:抽象クラスとインターフェース ← 今ここ
  10. 第10回:総合演習 - RPGバトルシステム構築 (次回)

© 2025 Java Quest - オブジェクト指向って何?RPGで理解する超入門
All Rights Reserved.

本記事は教育目的で作成されています。RPGの例を通じてプログラミング概念を楽しく学習できることを目指しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?