Open1

クラスとオブジェクト指向プログラミング

ひでひで

よくわかりやすい説明で使用されるのがポケモンの例えかと思います。
簡単なコードで説明します。
実際のポケモンのゲームのコードはかなり複雑であるかと思います。
今回はクラスとオブジェクト指向を理解するということを重点的においています。
あと、自分はpythonを勉強しているのでpythonで記事を書いていますが、
実際のポケモンはおそらくC#やC++などで書かれているかと思います。知らんけど。

ポケモンでクラス設計を理解する

# ポケモンのクラス設計
class Pokemon:
    def __init__(self, name, pokemon_type, hp, attack):
        # 属性(プロパティ)
        self.name = name           # ピカチュウ
        self.pokemon_type = pokemon_type  # でんき
        self.hp = hp              # 種族値
        self.attack = attack      # 種族値
        self.level = 1            # 初期レベル
    
    # メソッド(技・行動)
    def thunderbolt(self):
        return f"{self.name}のかみなり!威力{self.attack}のダメージ!"
    
    def quick_attack(self):
        return f"{self.name}でんこうせっか!"
    
    def level_up(self):
        self.level += 1
        self.hp += 5
        return f"{self.name}はレベル{self.level}になった!"

# ポケモンを作成(インスタンス化)
pikachu = Pokemon("ピカチュウ", "でんき", 35, 55)
raichu = Pokemon("ライチュウ", "でんき", 60, 90)

# メソッド(技)を使用
print(pikachu.thunderbolt())  # ピカチュウのかみなり!威力55のダメージ!
print(pikachu.level_up())     # ピカチュウはレベル2になった!

オブジェクト指向の4大要素

  1. カプセル化 → データと機能をまとめる
  2. 継承 → 親クラスの特徴を受け継ぐ
  3. ポリモーフィズム → 同じ名前で違う動作
  4. 抽象化 → 複雑な内部を隠す

継承を使用する

クラスの継承を使用することでより可読性や保守性の高い
簡潔でわかりやすいコードを書くことができます。
とても便利です。

# 継承の例
class ElectricPokemon(Pokemon):  # Pokemonクラスを継承
    def __init__(self, name, hp, attack):
        super().__init__(name, "でんき", hp, attack)  # 親クラスの__init__を呼び出し
    
    def electric_special(self):  # でんきタイプ専用の技
        return f"{self.name}の10まんボルト!"

pikachu = ElectricPokemon("ピカチュウ", 35, 55)
print(pikachu.electric_special())  # ピカチュウの10まんボルト!

__init__self の謎

__init__selfはクラスに必ずと言っていいほど記載されているので、
おまじないみたいに書けばいいやと思っていたら全然違いました。
プログラミング言語自体、とても優れた頭のいい人達が作っているので、一つ一つに意味があり、
ちゃんと意味があります。
自分よりもはるかに優れた人たちが作っているんだと常に意識しておくと、
便利な機能が用意されていることに気づきやすくなるのではないかと思います。

__init__ は「生まれた瞬間の設定」

class Pokemon:
    # __init__ = ポケモンが生まれた瞬間に実行される
    def __init__(self, name, pokemon_type):
        print(f"新しいポケモン「{name}」が誕生しました!")
        self.name = name
        self.pokemon_type = pokemon_type

# インスタンス作成時に自動で__init__が呼ばれる
pikachu = Pokemon("ピカチュウ", "でんき")
# → "新しいポケモン「ピカチュウ」が誕生しました!" が出力される

self = 「自分自身」を指す代名詞

class Pokemon:
    def __init__(self, name):
        self.name = name  # self = このインスタンス自身
    
    def introduce(self):
        # selfで自分のデータにアクセス
        return f"ポケモン図鑑No.??? {self.name}だ!"

# 複数のインスタンスを作成
pikachu = Pokemon("ピカチュウ")
charizard = Pokemon("リザードン")

# それぞれのselfは別のインスタンスを指す
print(pikachu.introduce())   # "ポケモン図鑑No.??? ピカチュウだ!"
print(charizard.introduce()) # "ポケモン図鑑No.??? リザードンだ!"

内部的に何が起きているか

# pikachu.introduce() は実際には...
Pokemon.introduce(pikachu)  # 第一引数にpikachuが自動で渡される
# だから第一引数は必ずselfになる!

selfの必要性

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner      # この口座の所有者
        self.balance = balance  # この口座の残高
    
    def withdraw(self, amount):
        # selfがないと、どの口座から引き出すかわからない!
        if self.balance >= amount:
            self.balance -= amount
            return f"{self.owner}さん、{amount}円引き出しました。残高:{self.balance}円"
        else:
            return "残高不足です"

# 異なる口座を作成
taro_account = BankAccount("太郎", 10000)
hanako_account = BankAccount("花子", 5000)

# それぞれの口座から引き出し
print(taro_account.withdraw(3000))   # 太郎さんの口座から
print(hanako_account.withdraw(2000)) # 花子さんの口座から