【Python中級への道 #9】OOPの真髄!「継承」でクラスの能力を引き継ぎ、進化させよう

ALL

こんにちは、Tech Samuraiです!
前回の記事「【Python中級への道 #8】オブジェクト指向をはじめよう!クラスとインスタンスの概念」では、クラスという「設計図」からオブジェクトという「実物」を作る方法を学びましたね。

前回作成したPlayerクラスは、どんなキャラクターにも共通する基本的な機能を持っていました。しかし、ゲームの世界には「戦士」や「魔法使い」といった、より専門的な職業が存在します。戦士は「怒りゲージ」を持ち、魔法使いは「MP」と「呪文を唱える」能力を持っているかもしれません。

これらの新しいクラスを作るたびに、Playerクラスのコードをコピー&ペーストするのは賢い方法ではありません。そこで登場するのが、OOPの真髄とも言える**「継承(けいしょう)」**という仕組みです。

継承を使えば、既存のクラスの能力(属性とメソッド)をまるごと引き継いだ、新しいクラスを簡単に作ることができます。さあ、クラスを親子のように関係付け、コードを再利用し、より複雑な世界を創造する冒険に出ましょう!


継承とは? 親と子の関係

継承とは、あるクラス(**親クラス**)の性質を、別のクラス(**子クラス**)が受け継ぐ仕組みのことです。

例えるなら… 生き物の進化です。

  • 「哺乳類」という親クラスは、「体温を保つ」「乳で子を育てる」といった基本的な属性やメソッドを持っています。
  • 「犬」や「猫」という子クラスは、「哺乳類」の性質をすべて**継承**します。そのため、犬も猫も体温を保ちます。
  • さらに、子クラスは独自の性質を追加します。「犬」クラスは「ワンと吠える」メソッドを持ち、「猫」クラスは「ニャーと鳴く」メソッドを持っています。

このように、子クラスは親クラスの能力をすべて受け継いだ上で、自分だけの新しい能力を追加したり、一部の能力を自分流に変化させたり(オーバーライド)できるのです。


Pythonでの継承の書き方

Pythonで継承を行うのは非常に簡単です。クラスを定義する際に、クラス名の後の括弧 `( )` の中に、親クラスの名前を書くだけです。

class 子クラス名(親クラス名):
    # 子クラス独自の定義
    pass

それでは、前回のPlayerクラスを親として、まずは空っぽのWarrior(戦士)クラスを作ってみましょう。

# 親クラスとなるPlayerクラス(前回のおさらい)
class Player:
    def __init__(self, name, hp, attack_power):
        self.name = name
        self.hp = hp
        self.attack_power = attack_power

    def attack(self, target_name):
        print(f"{self.name} の攻撃!")
        print(f"{target_name} に {self.attack_power} のダメージを与えた!")

# Playerクラスを継承したWarriorクラス
class Warrior(Player):
    pass # passは何もしないという意味。今は空っぽにしておく。

# --- 使ってみよう ---
# Warriorクラスからオブジェクトを生成
conan = Warrior("剣士コナン", 150, 25)

# Warriorクラスにはattackメソッドを定義していないのに…
# 親であるPlayerクラスから引き継いでいるので、問題なく使える!
conan.attack("ドラゴン")
print(f"{conan.name}のHPは{conan.hp}です。")

実行結果:

剣士コナン の攻撃!
ドラゴン に 25 のダメージを与えた!
剣士コナンのHPは150です。

驚きましたか? `Warrior`クラスの中身は空っぽなのに、ちゃんとattackメソッドが使えました。これこそが継承の力です!


子クラスの拡張とオーバーライド

子クラスが親と全く同じでは意味がありません。ここからは、子クラスに独自の機能を追加したり、親の機能を上書きしたりする方法を見ていきましょう。

子クラスに新しい属性とメソッドを追加する

戦士`Warrior`に、独自の属性「怒りゲージ(`rage`)」と、独自のメソッド「突撃 (`charge`)」を追加してみます。

class Warrior(Player):
    def __init__(self, name, hp, attack_power, rage_point):
        # まず、親クラスの__init__を呼び出して、共通の属性(name, hp, ap)を設定してもらう
        super().__init__(name, hp, attack_power)
        # その後で、Warrior独自の属性を追加
        self.rage_point = rage_point

    # Warrior独自のメソッド
    def charge(self, target_name):
        print(f"{self.name} の突撃! {target_name} に突っ込んでいく!")

# Warriorを生成
conan = Warrior("剣士コナン", 150, 25, 100)
conan.attack("ドラゴン") # 親から継承したメソッド
conan.charge("ドラゴン") # 自分だけのメソッド
print(f"怒りゲージ: {conan.rage_point}") # 自分だけの属性

【重要ポイント:super()
子クラスで__init__を定義する際、super().__init__(...)というおまじないを最初に書きます。これは、「**親クラスの初期化処理を、まず実行してください**」という意味です。これを実行することで、self.nameself.hpといった共通の属性が正しく設定されます。

親クラスのメソッドを上書き(オーバーライド)する

魔法使い`Magician`クラスを作り、攻撃方法を親の`Player`とは違う「魔法」にしてみましょう。親クラスと同じ名前のメソッドを子クラスで再定義することを**オーバーライド**と言います。

class Magician(Player):
    def __init__(self, name, hp, attack_power, mp):
        super().__init__(name, hp, attack_power) # 親の__init__を呼び出し
        self.mp = mp

    # 親のattackメソッドを「上書き」する
    def attack(self, target_name):
        print(f"{self.name} は呪文を唱えた!")
        print(f"ファイアボールが {target_name} を襲う!")
        print(f"{target_name} に {self.attack_power * 1.5} の大ダメージ!")

# Magicianを生成
merlin = Magician("魔法使いマーリン", 80, 10, 200)

# Magicianのattackを呼び出すと、オーバーライドした方が実行される
merlin.attack("ゴーレム")
print(f"MP: {merlin.mp}")

同じ`attack()`という呼び出しでも、オブジェクトが`Warrior`か`Magician`かによって、その振る舞いが変わるのが分かりますね。


まとめ

今回は、オブジェクト指向プログラミングの真髄である「継承」について探検しました。

  • 継承は、**親クラス**の能力を**子クラス**が引き継ぐ仕組みであること。
  • 書き方は class 子クラス(親クラス): とシンプル。
  • 子クラスは、super()を使って親の能力を呼び出しつつ、独自の**属性やメソッドを追加**できること。
  • 子クラスは、親のメソッドを再定義して、振る舞いを変更(オーバーライド)できること。

継承をマスターしたことで、あなたはコードの再利用性を最大限に高め、拡張性の高い、美しいプログラムを設計する能力を手に入れました。

さて、クラスという設計図でデータと処理をまとめ、継承でその関係性を定義できるようになりました。しかし、クラスの内部のデータを、外から不用意に書き換えられてしまう危険性がまだ残っています。次回は、クラスのデータを安全に守るための「壁」を作る、**「カプセル化」**という概念を探検します。お楽しみに!

コメント

タイトルとURLをコピーしました