Python 継承とポリモーフィズム

2025-10-27

はじめに

今回はオブジェクト指向プログラミングの「継承」と「ポリモーフィズム」について詳しく探っていきます。これらの概念は、コードの再利用性を高め、柔軟で拡張性の高いプログラムを構築するための強力なツールです。

現実世界で例えるなら、継承は「親から子へ特徴が受け継がれる」関係に似ています。例えば、「自動車」という基本クラスがあり、そこから「スポーツカー」「SUV」「トラック」などの特殊なクラスが派生するようなイメージです。

継承の基本

継承とは、既存のクラスを基にして新しいクラスを作成する仕組みです。基になるクラスを「親クラス」「スーパークラス」「基底クラス」と呼び、新しく作成するクラスを「子クラス」「サブクラス」「派生クラス」と呼びます。

次のコードは 継承とメソッドのオーバーライド の基本例です。

Animal クラスを親クラスとして定義し、DogCat クラスがそれを継承しています。
共通の属性(name, age)やメソッド(sleep())は親クラスから引き継がれ、それぞれのクラスで speak() メソッドをオーバーライド(上書き) して、犬と猫に特有の鳴き声を出すように動作を変更しています。

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak(self):
        print("動物の鳴き声")

    def sleep(self):
        print(f"{self.name}は眠っています")

class Dog(Animal):  # Animalクラスを継承
    def __init__(self, name, age, breed):
        super().__init__(name, age)  # 親クラスのコンストラクタを呼び出し
        self.breed = breed

    def speak(self):  # メソッドのオーバーライド
        print(f"{self.name}はワンワン吠えます")

class Cat(Animal):  # Animalクラスを継承
    def __init__(self, name, age, color):
        super().__init__(name, age)
        self.color = color

    def speak(self):  # メソッドのオーバーライド
        print(f"{self.name}はニャーと鳴きます")

継承の利点

  1. コードの再利用: 親クラスの機能をそのまま利用できる
  2. 保守性の向上: 共通機能を一箇所で管理できる
  3. 拡張性: 新しい機能を簡単に追加できる
  4. 階層的な構造: 現実世界の関係を自然に表現できる

super()関数の役割

super()関数は、親クラスのメソッドを呼び出すための重要な関数です。特にコンストラクタで親クラスの初期化を行う際に頻繁に使用されます。

class Vehicle:
    def __init__(self, brand, model, year):
        self.brand = brand
        self.model = model
        self.year = year
        self.speed = 0

    def start_engine(self):
        print("エンジンを始動しました")

    def stop_engine(self):
        print("エンジンを停止しました")

class Car(Vehicle):
    def __init__(self, brand, model, year, doors):
        super().__init__(brand, model, year)  # 親クラスのコンストラクタを呼び出し
        self.doors = doors  # 子クラス独自の属性

    def open_trunk(self):  # 子クラス独自のメソッド
        print("トランクを開けました")

# 使用例
my_car = Car("Toyota", "Camry", 2023, 4)
print(f"{my_car.brand} {my_car.model}")  # 親クラスの属性にアクセス
my_car.start_engine()  # 親クラスのメソッドを呼び出し

Vehicleクラスが共通の属性(ブランド、モデル、年式)や動作(エンジンの始動・停止)を定義し、Carクラスがそれを継承して自動車特有の属性doorsやメソッドopen_trunk()を追加しています。これにより、共通の機能を親クラスにまとめつつ、子クラスで独自の機能を拡張できる仕組みが理解できます

メソッドのオーバーライド

オーバーライドとは、親クラスで定義されたメソッドを子クラスで再定義することです。これにより、同じメソッド名でも異なる振る舞いを実現できます。オーバーライドにより、同じメソッド名でもクラスごとに異なる動作を実現でき、ポリモーフィズム(多態性)が成立します。以下のコードの例を参照します。

class Shape:
    def __init__(self, name):
        self.name = name

    def area(self):
        raise NotImplementedError("サブクラスで実装してください")

    def perimeter(self):
        raise NotImplementedError("サブクラスで実装してください")

    def display_info(self):
        print(f"図形: {self.name}")

class Rectangle(Shape):
    def __init__(self, width, height):
        super().__init__("長方形")
        self.width = width
        self.height = height

    def area(self):  # オーバーライド
        return self.width * self.height

    def perimeter(self):  # オーバーライド
        return 2 * (self.width + self.height)

class Circle(Shape):
    def __init__(self, radius):
        super().__init__("円")
        self.radius = radius

    def area(self):  # オーバーライド
        return 3.14159 * self.radius ** 2

    def perimeter(self):  # オーバーライド
        return 2 * 3.14159 * self.radius

親クラスShapeで定義されたarea()perimeter()を、子クラスRectangleCircleがそれぞれの図形に合わせて再定義することで、同じメソッド名でも異なる処理を実現しています。これにより、ポリモーフィズムが成立し、例えばshapes = [Rectangle(3, 4), Circle(5)]のように異なる図形をまとめて扱っても、shape.area()の呼び出しだけで各図形に適した面積が計算されます。結果として、柔軟で拡張性の高いコード設計が可能になります。

ポリモーフィズムの概念

ポリモーフィズム(多態性)とは、同じインターフェースで異なる振る舞いを実現する仕組みです。異なるクラスのオブジェクトが同じメソッド名で呼び出されても、各クラスに適した動作を行うことができます。

ポリモーフィズムの実例

Birdクラスを基底に、SparrowEaglePenguinがそれぞれfly()メソッドを独自に定義しています。make_bird_fly()関数は同じfly()呼び出しを行いますが、実際の動作はオブジェクトの種類に応じて異なる結果を出力します。

class Bird:
    def fly(self):
        pass

class Sparrow(Bird):
    def fly(self):
        print("スズメはバサバサ飛びます")

class Eagle(Bird):
    def fly(self):
        print("ワシは悠然と飛びます")

class Penguin(Bird):
    def fly(self):
        print("ペンギンは飛べません")

def make_bird_fly(bird):
    bird.fly()  # 同じメソッド呼び出しでも、オブジェクトによって異なる動作

# 使用例
birds = [Sparrow(), Eagle(), Penguin()]

for bird in birds:
    make_bird_fly(bird)

出力結果:

スズメはバサバサ飛びます
ワシは悠然と飛びます
ペンギンは飛べません

多重継承

Pythonでは、複数の親クラスから継承する「多重継承」が可能です。これは強力な機能ですが、注意して使用する必要があります。

class Flyable:
    def fly(self):
        print("飛んでいます")

class Swimmable:
    def swim(self):
        print("泳いでいます")

class Duck(Flyable, Swimmable):
    def __init__(self, name):
        self.name = name

    def quack(self):
        print(f"{self.name}がガーガー鳴きます")

# 使用例
duck = Duck("ドナルド")
duck.fly()   # Flyableクラスのメソッド
duck.swim()  # Swimmableクラスのメソッド
duck.quack() # Duckクラス独自のメソッド

多重継承は、複数の親クラスから同時に機能を継承できる便利な仕組みですが、注意が必要です。
理由は次の通りです。

  1. メソッドの衝突:複数の親クラスで同名のメソッドがあると、どのクラスのメソッドが呼ばれるか分かりづらくなります。
  2. 呼び出し順序の複雑さ:Pythonは「MRO(Method Resolution Order)」というルールでメソッド探索順を決めますが、構造が複雑になると予期しない動作を引き起こすことがあります。
  3. 可読性・保守性の低下:継承関係が増えるほど、コードの理解や変更が難しくなります。

そのため、多重継承は慎重に設計し、必要最小限に使うのが望ましいです。

抽象基底クラス

抽象基底クラスは、インスタンス化できないクラスで、他のクラスのテンプレートとして機能します。Pythonではabcモジュールを使用して実現します。

abcモジュール」というのは正式名称ではなく、Python標準ライブラリに含まれるモジュール名 abc(=Abstract Base Classes の略)をそのまま通称として呼んでいるものです。

abcモジュールは、Pythonで**抽象基底クラス(Abstract Base Class)**を定義するためのモジュールです。共通のメソッドやインターフェースを強制することで、継承先のクラスが特定のメソッドを必ず実装するようにできます。@abstractmethodデコレータを使って抽象メソッドを定義します。

from abc import ABC, abstractmethod

class Employee(ABC):
    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id

    @abstractmethod
    def calculate_salary(self):
        pass

    @abstractmethod
    def display_role(self):
        pass

    def display_info(self):
        print(f"名前: {self.name}")
        print(f"社員ID: {self.employee_id}")

class FullTimeEmployee(Employee):
    def __init__(self, name, employee_id, monthly_salary):
        super().__init__(name, employee_id)
        self.monthly_salary = monthly_salary

    def calculate_salary(self):
        return self.monthly_salary

    def display_role(self):
        print("正社員")

class PartTimeEmployee(Employee):
    def __init__(self, name, employee_id, hourly_wage, hours_worked):
        super().__init__(name, employee_id)
        self.hourly_wage = hourly_wage
        self.hours_worked = hours_worked

    def calculate_salary(self):
        return self.hourly_wage * self.hours_worked

    def display_role(self):
        print("パートタイム社員")

isinstance()とissubclass()

isinstance()とissubclass()関数は、継承関係を確認するために使用されます。isinstance() はオブジェクトが特定のクラス(またはそのサブクラス)のインスタンスかを判定し、issubclass() はクラス同士の継承関係を確認します。

ここでは my_carCarVehicle のインスタンスであり、CarVehicle のサブクラスであることがわかります。

# 継承関係の確認
print(isinstance(my_car, Car))      # True
print(isinstance(my_car, Vehicle))  # True
print(issubclass(Car, Vehicle))     # True
print(issubclass(Vehicle, Car))     # False

継承の設計原則

継承を使用する際には、以下の原則を考慮することが重要です。

  1. Liskovの置換原則: 子クラスは親クラスと置換可能でなければならない
  2. 単一責任の原則: 各クラスは単一の責任のみを持つべき
  3. 依存性逆転の原則: 具象クラスではなく抽象クラスに依存する

実践的な例:ゲームのキャラクターシステム

継承とポリモーフィズムの実践的な使用例として、ゲームのキャラクターシステムを実装してみましょう。このコードはGameCharacterを基底クラスとして、WarriorMageArcherがそれぞれ独自の攻撃方法を実装しています。

共通メソッド(attack, take_damage, heal, など)を共有しつつ、クラスごとに異なる挙動を示すことで、柔軟なバトルシステムを実現しています。
battle()関数では、それぞれのキャラクターが交互に攻撃し、倒れるまで戦う流れを表しています。

class GameCharacter:
    def __init__(self, name, level, health):
        self.name = name
        self.level = level
        self.health = health
        self.max_health = health

    def attack(self):
        raise NotImplementedError("サブクラスで実装してください")

    def take_damage(self, damage):
        self.health = max(0, self.health - damage)
        print(f"{self.name}は{damage}のダメージを受けた!")
        if self.health == 0:
            print(f"{self.name}は倒れた!")

    def heal(self, amount):
        self.health = min(self.max_health, self.health + amount)
        print(f"{self.name}は{amount}回復した!")

    def is_alive(self):
        return self.health > 0

    def display_status(self):
        print(f"{self.name} Lv{self.level} HP: {self.health}/{self.max_health}")

class Warrior(GameCharacter):
    def __init__(self, name, level, health, strength):
        super().__init__(name, level, health)
        self.strength = strength

    def attack(self):
        damage = self.strength + self.level
        print(f"{self.name}の剣の攻撃! {damage}のダメージ!")
        return damage

    def power_attack(self):
        damage = self.strength * 2 + self.level
        self.take_damage(5)  # 反動ダメージ
        print(f"{self.name}の強力な一撃! {damage}のダメージ!")
        return damage

class Mage(GameCharacter):
    def __init__(self, name, level, health, magic_power):
        super().__init__(name, level, health)
        self.magic_power = magic_power
        self.mana = 100

    def attack(self):
        damage = self.magic_power // 2
        print(f"{self.name}の魔法の攻撃! {damage}のダメージ!")
        return damage

    def fire_ball(self):
        if self.mana >= 20:
            damage = self.magic_power + self.level
            self.mana -= 20
            print(f"{self.name}のファイアボール! {damage}のダメージ!")
            return damage
        else:
            print("マナが足りない!")
            return 0

    def display_status(self):
        super().display_status()
        print(f"MP: {self.mana}/100")

class Archer(GameCharacter):
    def __init__(self, name, level, health, dexterity):
        super().__init__(name, level, health)
        self.dexterity = dexterity
        self.arrows = 10

    def attack(self):
        if self.arrows > 0:
            damage = self.dexterity + self.level // 2
            self.arrows -= 1
            print(f"{self.name}の弓の攻撃! {damage}のダメージ!")
            return damage
        else:
            print("矢が足りない!")
            return 0

    def multi_shot(self):
        if self.arrows >= 3:
            damage = self.dexterity * 1.5
            self.arrows -= 3
            print(f"{self.name}のマルチショット! {damage}のダメージ!")
            return damage
        else:
            print("矢が足りない!")
            return 0

    def display_status(self):
        super().display_status()
        print(f"矢: {self.arrows}本")

# ポリモーフィズムの実演
def battle(character1, character2):
    print("=== バトル開始 ===")

    while character1.is_alive() and character2.is_alive():
        # キャラクター1の攻撃
        damage = character1.attack()
        character2.take_damage(damage)

        if not character2.is_alive():
            break

        # キャラクター2の攻撃
        damage = character2.attack()
        character1.take_damage(damage)

    if character1.is_alive():
        print(f"{character1.name}の勝利!")
    else:
        print(f"{character2.name}の勝利!")

# 使用例
warrior = Warrior("勇者", 10, 100, 15)
mage = Mage("魔法使い", 8, 80, 25)

characters = [warrior, mage]

for character in characters:
    character.display_status()
    character.attack()
    print()

# バトルの実行
battle(warrior, mage)

まとめ

継承とポリモーフィズムは、オブジェクト指向プログラミングの強力な概念です。継承によりコードの再利用性と保守性が向上し、ポリモーフィズムにより柔軟で拡張性の高い設計が可能になります。

親子関係を持つ構造を利用することで、現実世界のような階層的な関係を自然に表現できます。これにより、複雑なシステムでも構造を整理しやすくなります。既存の仕組みを活用しながら機能を追加できるため、新しい機能への対応も容易になります。修正範囲を最小限に抑えやすく、将来的な拡張にも柔軟に対応できます。

加えて、同じインターフェースを利用しながら内部の処理だけを変えることで、異なる振る舞いを実現できます。そのため、利用する側は細かな違いを意識せずに、統一された方法で機能を扱えるようになります。

演習問題

初級問題

問題1
次のコードを完成させて、Animalクラスを継承したDogクラスとCatクラスを作成してください。各クラスはspeakメソッドを持ち、それぞれ異なる鳴き声を出力するようにしてください。

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

実行結果:

ポチはワンワン吠えます
タマはニャーと鳴きます

問題2
Vehicleクラスを継承したCarクラスを作成してください。Carクラスはdoors属性を追加し、display_infoメソッドをオーバーライドしてドア数も表示するようにしてください。

class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def display_info(self):
        print(f"{self.brand} {self.model}")

実行結果:

Toyota Camry
Honda Civic - 4ドア

問題3
次のクラス階層で、RectangleクラスとCircleクラスにareaメソッドを実装してください。

class Shape:
    def area(self):
        pass

実行結果:

面積: 15.00
面積: 12.57

中級問題

問題4

Employeeクラスを親クラスとして作成し、ManagerクラスとDeveloperクラスを継承して作成してください。Managerクラスのボーナスは基本給の30%とします。Developerクラスのボーナスは基本給の20%に加えて、完了プロジェクト数 × 5000円とします。以下の情報を表示してください。

  • 名前
  • 基本給
  • ボーナス
  • 完了プロジェクト数(Developerのみ)

使用データ

  • Manager(“山田部長”, 800000)
  • Developer(“佐藤エンジニア”, 600000, 5)

まずEmployeeクラスを作成します。ここでは社員共通の「名前」と「基本給」を保存できるようにします。その後、display_infoメソッドを作成して社員情報を表示できるようにします。

次にManagerクラスを作成します。Employeeクラスを継承し、calculate_bonusメソッドで「基本給 × 0.3」を計算します。display_infoメソッドでは親クラスのdisplay_infoを呼び出したあとにボーナスを表示します。

その後、Developerクラスを作成します。Developerクラスでは追加で「完了プロジェクト数」を保存します。calculate_bonusメソッドでは「基本給 × 0.2 + プロジェクト数 × 5000」を計算します。最後にManagerオブジェクトとDeveloperオブジェクトを作成し、for文で順番にdisplay_infoメソッドを実行して結果を表示します。

実行結果:

山田部長 - 基本給: 800000円
ボーナス: 240000.0円

佐藤エンジニア - 基本給: 600000円
完了プロジェクト: 5
ボーナス: 145000.0円

問題5

BankAccountクラスを親クラスとして作成し、SavingsAccountクラスとCheckingAccountクラスを継承して作成してください。SavingsAccountクラスでは利息計算を行います。利率は0.03とし、6ヶ月分の利息を計算してください。CheckingAccountクラスでは、4回目以降の引き出し時に取引手数料150円を差し引いてください。以下の操作を実行してください。

  • 普通預金口座へ50000円を預け入れる
  • 6ヶ月分の利息を計算する
  • 利息を残高へ追加する
  • 当座預金口座から1000円を5回引き出す
  • 最後に口座情報と残高を表示する

使用データ

  • SavingsAccount(“SAV001”, 100000, 0.03)
  • CheckingAccount(“CHK001”, 50000, 150)

まずBankAccountクラスを作成します。ここでは「口座番号」と「残高」を保存します。その後、depositメソッドで入金処理、withdrawメソッドで出金処理、display_balanceメソッドで口座情報を表示できるようにします。

次にSavingsAccountクラスを作成します。BankAccountクラスを継承し、追加で「利率」を保存します。calculate_interestメソッドでは「残高 × 利率 × 月数 ÷ 12」で利息を計算します。apply_interestメソッドでは計算した利息を残高へ追加します。

その後、CheckingAccountクラスを作成します。ここでは「取引手数料」と「取引回数」を追加します。withdrawメソッドを上書きし、4回目以降の出金時には手数料150円を残高から引きます。最後にSavingsAccountオブジェクトとCheckingAccountオブジェクトを作成し、問題文の順番どおりにメソッドを実行して結果を表示します。

実行結果:

残高: 150375.0円

=== 当座預金口座 ===
1000円引き出しました
1000円引き出しました
1000円引き出しました
1000円引き出しました
取引手数料: 150円
1000円引き出しました
取引手数料: 150円
口座番号: CHK001
残高: 44700円

問題6

Itemクラスを親クラスとして作成し、Weaponクラス、Armorクラス、Potionクラスを継承して作成してください。Weaponクラスでは攻撃力を持つ武器を作成します。Armorクラスでは防御力を持つ防具を作成します。PotionクラスではHPを回復する回復アイテムを作成します。また、GameCharacterクラスを作成し、アイテムを持てるようにしてください。以下の処理を実行してください。

  • 武器「鋼の剣」を追加する
  • 防具「鉄の鎧」を追加する
  • 回復アイテム「回復薬」を追加する
  • 追加したアイテムを順番に使用する

使用データ

  • Weapon(“鋼の剣”, 100, 15)
  • Armor(“鉄の鎧”, 150, 10)
  • Potion(“回復薬”, 50, 30)

表示内容

  • アイテム名
  • 攻撃力または防御力
  • 回復量
  • アイテム使用メッセージ

まずItemクラスを作成します。ここでは「アイテム名」と「価値」を保存できるようにします。また、useメソッドを用意し、子クラスで処理を作れるようにします。次にWeaponクラスを作成します。Itemクラスを継承し、「攻撃力」を追加します。useメソッドでは「○○で攻撃!」と表示し、攻撃力を返します。

その後、Armorクラスを作成します。ここでは「防御力」を追加し、useメソッドで「防御力+○○」を表示します。続いてPotionクラスを作成します。「回復量」を追加し、useメソッドで「HPを○○回復!」と表示します。

次にGameCharacterクラスを作成します。ここではキャラクター名、HP、持ち物リストを保存します。add_itemメソッドでアイテムを追加し、use_itemメソッドでアイテムを使用できるようにします。最後にGameCharacterオブジェクトを作成し、武器、防具、回復薬を追加します。その後、for文で順番にアイテムを使用し、結果を表示します。

実行結果:

鋼の剣を入手しました
鉄の鎧を入手しました
回復薬を入手しました
=== アイテム使用 ===
鋼の剣で攻撃! 15のダメージ!

鉄の鎧を装備! 防御力+10

回復薬を使用! HPを30回復!

問題7

Personクラスを親クラスとして作成し、StudentクラスとTeacherクラスを継承して作成してください。また、Researcherクラスを作成し、GraduateStudentクラスではStudentクラスとResearcherクラスの両方を継承してください。GraduateStudentクラスでは以下の情報を扱えるようにしてください。

  • 名前
  • 年齢
  • 学籍番号
  • 研究分野
  • 履修科目
  • 論文一覧

以下の処理を実行してください。

  • 大学院生を作成する
  • 自己紹介を表示する
  • 「機械学習」を履修登録する
  • 「深層学習の応用」という論文を追加する
  • 研究内容を表示する

使用データ

  • GraduateStudent(“鈴木花子”, 25, “G12345”, “人工知能”)

表示内容

  • 名前
  • 年齢
  • 学籍番号
  • 研究分野
  • 論文数
  • 履修登録メッセージ
  • 研究メッセージ

まずPersonクラスを作成します。ここでは「名前」と「年齢」を保存します。introduceメソッドでは自己紹介を表示できるようにします。次にStudentクラスを作成します。Personクラスを継承し、「学籍番号」と「履修科目リスト」を追加します。enroll_courseメソッドでは履修科目を追加できるようにします。

その後、Teacherクラスを作成します。ここでは担当科目を保存し、teachメソッドで授業内容を表示します。続いてResearcherクラスを作成します。ここでは「研究分野」と「論文リスト」を保存します。add_publicationメソッドでは論文を追加し、conduct_researchメソッドでは研究内容を表示します。

次にGraduateStudentクラスを作成します。StudentクラスとResearcherクラスの両方を継承し、多重継承を利用します。コンストラクタでは両方の親クラスの初期化を行います。最後にGraduateStudentオブジェクトを作成し、自己紹介、履修登録、論文追加、研究内容表示を順番に実行します。

実行結果:

私は鈴木花子です。25歳です。
学籍番号: G12345
研究分野: 人工知能
論文数: 0
機械学習を履修登録しました
論文 '深層学習の応用' を追加しました
人工知能の研究を行っています

問題8

Animalクラスを抽象クラスとして作成し、以下のクラスを継承して作成してください。

  • Mammalクラス(哺乳類)
  • Birdクラス(鳥類)
  • Reptileクラス(爬虫類)

さらに、それぞれのクラスを継承して以下のクラスを作成してください。

  • Lionクラス
  • Eagleクラス
  • Snakeクラス

以下の動物データを使用してください。

  • Lion(“シンバ”, “サバンナ”, “金色”, “大きい”)
  • Eagle(“ホーク”, “山岳地帯”, 200, “2km”)
  • Snake(“ナーガ”, “ジャングル”, “光沢のある”, 3.0)

まずAnimalクラスを作成します。ここでは「名前」と「生息地」を保存します。moveメソッドは抽象メソッドとして定義します。display_infoメソッドでは基本情報を表示します。

次にMammalクラス、Birdクラス、Reptileクラスを作成します。これらはAnimalクラスを継承します。それぞれに独自の属性を追加します。Mammalでは毛色、Birdでは翼幅、Reptileでは鱗の種類を保存します。その後、moveメソッドを各クラスで実装します。哺乳類は「走ります」、鳥類は「飛びます」、爬虫類は「這います」と表示するようにします。

続いてLionクラス、Eagleクラス、Snakeクラスを作成します。これらはさらに親クラスを継承します。それぞれ追加の属性を持たせます。Lionではたてがみの大きさ、Eagleでは視界範囲、Snakeでは体長を保存します。

各サブクラスではmoveメソッドをオーバーライドして、より具体的な移動方法を表示します。最後に3つの動物オブジェクトを作成し、animalsリストに格納します。for文で取り出し、display_infoメソッドとmoveメソッドを順番に実行します。

実行結果:

シンバ - 生息地: サバンナ
毛色: 金色
シンバは悠然と歩きます

ホーク - 生息地: 山岳地帯
翼幅: 200cm
ホークは高く舞い上がります

ナーガ - 生息地: ジャングル
鱗の種類: 光沢のある
ナーガは静かに這います

問題9

ElectronicDeviceクラスを親クラスとして作成し、以下のクラスを継承して作成してください。

  • SmartPhoneクラス
  • Laptopクラス
  • Tabletクラス

SmartPhoneクラスでは以下の機能を実装してください。

  • 電話をかける
  • アプリをインストールする

Laptopクラスでは以下の機能を実装してください。

  • コーディングを実行する
  • バッテリー残量を表示する

Tabletクラスでは以下の機能を実装してください。

  • 絵を描く
  • 描画アプリをインストールする

以下のデータを使用してください。

  • SmartPhone(“Apple”, “iPhone 15”, 5, “iOS”, 6.1)
  • Laptop(“Dell”, “XPS 13”, 45, “Intel i7”, 16)
  • Tablet(“Samsung”, “Galaxy Tab”, 8, True, 128)

各デバイスでは以下を実行してください。

  • 電源を入れる
  • デバイス情報を表示する
  • デバイス専用機能を実行する

まずElectronicDeviceクラスを作成します。ここではブランド名、モデル名、消費電力を保存します。また、電源状態を管理するためにis_onを用意します。次にturn_onメソッドとturn_offメソッドを作成します。ここでは電源状態を変更し、メッセージを表示します。

その後、display_infoメソッドを作成します。ここではデバイス名と電源状態を表示します。続いてSmartPhoneクラスを作成します。ElectronicDeviceクラスを継承し、OS名と画面サイズを追加します。電話機能とアプリインストール機能も作成します。

次にLaptopクラスを作成します。プロセッサ名、RAM容量、バッテリー残量を追加します。codeメソッドではコーディング処理を行い、バッテリーを減少させます。その後、Tabletクラスを作成します。スタイラス対応情報とストレージ容量を追加します。drawメソッドでは絵を描く処理を実装します。

各クラスではdisplay_infoメソッドをオーバーライドし、親クラスの情報に加えて独自情報も表示できるようにします。最後に3つのデバイスオブジェクトを作成し、devicesリストへ格納します。for文で取り出し、電源を入れてから情報表示と専用機能を実行します。

実行結果:

Dell XPS 13の電源を入れました
Dell XPS 13 - 状態: ON
プロセッサ: Intel i7, RAM: 16GB
コーディング中...
バッテリー残量: 95%

Samsung Galaxy Tabの電源を入れました
Samsung Galaxy Tab - 状態: ON
スタイラス: 対応, ストレージ: 128GB
絵を描いています
描画アプリ 'Procreate' をインストールしました

上級問題

問題10

Drawableクラスを抽象クラスとして作成し、以下のメソッドを定義してください。

  • drawメソッド
  • get_areaメソッド
  • get_perimeterメソッド

その後、Drawableクラスを継承して以下のクラスを作成してください。

  • Pointクラス
  • Lineクラス
  • Rectangleクラス
  • Circleクラス

各クラスでは図形情報を保持し、drawメソッドで図形情報を表示してください。

また、面積と周囲長を計算してください。

以下のデータを使用してください。

  • Point(0, 0)
  • Line(Point(0, 0), Point(3, 4))
  • Rectangle(1, 1, 5, 3)
  • Circle(2, 2, 3)

Drawingクラスを作成し、複数の図形を管理してください。

最後に以下の処理を実行してください。

  • 図形を追加する
  • すべての図形を描画する
  • 総面積を表示する
  • 総周囲長を表示する

実行結果:

=== すべての図形を描画 ===
点を描画: (0, 0)
  面積: 0.00, 周囲長: 0.00
線を描画: (0, 0) -> (3, 4)
  面積: 0.00, 周囲長: 5.00
長方形を描画: 位置(1, 1), サイズ(5×3)
  面積: 15.00, 周囲長: 16.00
円を描画: 中心(2, 2), 半径3
  面積: 28.27, 周囲長: 18.85
========================
総面積: 43.27
総周囲長: 39.85

問題11

Productクラスを抽象クラスとして作成し、以下のメソッドを定義してください。

  • check_inventoryメソッド
  • update_inventoryメソッド
  • can_sellメソッド

その後、Productクラスを継承して以下のクラスを作成してください。

  • PhysicalProductクラス
  • DigitalProductクラス

PhysicalProductクラスでは以下の情報を管理してください。

  • 商品ID
  • 商品名
  • 価格
  • 在庫数
  • 重量

また、配送コストを計算するメソッドを作成してください。

DigitalProductクラスでは以下の情報を管理してください。

  • 商品ID
  • 商品名
  • 価格
  • ファイルサイズ
  • ダウンロード制限回数
  • ダウンロード回数

InventoryManagerクラスを作成し、複数の商品を管理してください。

以下のデータを使用してください。

  • PhysicalProduct(“P001”, “ノートパソコン”, 150000, 10, 2.5)
  • DigitalProduct(“D001”, “電子書籍”, 2000, 50, 3)

最後に以下の処理を実行してください。

  • 商品を追加する
  • 在庫レポートを表示する
  • 商品を販売する
  • 最終在庫レポートを表示する

販売処理では以下を実行してください。

  • ノートパソコンを2個販売
  • 電子書籍を1個販売

実行結果:

在庫数: 12
重量: 2.5kg
在庫状態: 12
---
ID: D001
商品名: 電子書籍
価格: 2000円
ファイルサイズ: 50MB
ダウンロード制限: 3回
現在のダウンロード回数: 1
在庫状態: 2回
---
===================

問題12

抽象クラスを利用して、複数種類の通知を管理できる通知システムを作成してください。通知には「メール通知」「SMS通知」「プッシュ通知」があり、それぞれ送信方法が異なります。すべての通知は共通して、受信者・メッセージ・送信状態を持つようにしてください。また、通知をまとめて管理するクラスを作成し、一括送信や統計表示ができるようにしてください。

各通知クラスでは send() メソッドをオーバーライドし、通知ごとの送信内容を表示してください。
送信後は送信日時と状態を更新してください。最後に、複数の通知を作成して管理クラスへ登録し、一覧表示・統計表示・一括送信を実行してください。

  • ABC@abstractmethod を利用します。
  • EmailNotificationSMSNotificationPushNotification の3つの子クラスを作成します。
  • send() をオーバーライドし、通知ごとの送信内容を表示します。
  • 通知をまとめて管理する NotificationManager クラスを作成します。
  • 複数の通知オブジェクトを作成し、マネージャークラスへ登録します。
  • 通知一覧を表示した後、実際に一括送信を実行し、送信後の状態を確認します。

実行結果:

通知を追加しました: メール通知
通知を追加しました: SMS通知
通知を追加しました: プッシュ通知

=== すべての通知 ===
1. 通知タイプ: メール通知
受信者: user@example.com
メッセージ: お知らせがあります
ステータス: 準備中
差出人: noreply@company.com
件名: 重要なお知らせ

2. 通知タイプ: SMS通知
受信者: 山田太郎
メッセージ: 本日15時にお伺いします
ステータス: 準備中
電話番号: 090-1234-5678

3. 通知タイプ: プッシュ通知
受信者: user123
メッセージ: 新しいメッセージが届きました
ステータス: 準備中
アプリ名: MyApp
デバイストークン: device_token_abc123


=== 通知統計 ===
メール通知: 1件
SMS通知: 1件
プッシュ通知: 1件
総送信済み数: 0件
================

=== 一括通知送信 ===
メール送信: noreply@company.com -> user@example.com
件名: 重要なお知らせ
本文: お知らせがあります
---
SMS送信: 090-1234-5678
メッセージ: 本日15時にお伺いします
---
プッシュ通知送信: MyApp
デバイス: device_token_abc123
メッセージ: 新しいメッセージが届きました
---
3件の通知を送信しました

=== 通知統計 ===
メール通知: 1件
SMS通知: 1件
プッシュ通知: 1件
総送信済み数: 3件
================

初級問題

問題1 解答

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        print(f"{self.name}はワンワン吠えます")

class Cat(Animal):
    def speak(self):
        print(f"{self.name}はニャーと鳴きます")

# 使用例
dog = Dog("ポチ")
cat = Cat("タマ")

dog.speak()  # ポチはワンワン吠えます
cat.speak()  # タマはニャーと鳴きます

解説: 親クラスのメソッドをオーバーライドして、各子クラスで異なる振る舞いを実装しています。

問題2 解答

class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def display_info(self):
        print(f"{self.brand} {self.model}")

class Car(Vehicle):
    def __init__(self, brand, model, doors):
        super().__init__(brand, model)
        self.doors = doors

    def display_info(self):
        print(f"{self.brand} {self.model} - {self.doors}ドア")

# 使用例
vehicle = Vehicle("Toyota", "Camry")
car = Car("Honda", "Civic", 4)

vehicle.display_info()  # Toyota Camry
car.display_info()      # Honda Civic - 4ドア

解説: super()で親クラスのコンストラクタを呼び出し、メソッドをオーバーライドして追加情報を表示しています。

問題3 解答

class Shape:
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

# 使用例
shapes = [Rectangle(5, 3), Circle(2)]

for shape in shapes:
    print(f"面積: {shape.area():.2f}")

# 出力:
# 面積: 15.00
# 面積: 12.57

解説: ポリモーフィズムを活用して、異なる図形オブジェクトを同じインターフェースで扱っています。

中級問題

問題4 解答

from abc import ABC, abstractmethod

class Employee(ABC):
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    @abstractmethod
    def calculate_bonus(self):
        pass

    def display_info(self):
        print(f"{self.name} - 基本給: {self.salary}円")

class Manager(Employee):
    def calculate_bonus(self):
        return self.salary * 0.3  # 基本給の30%

    def display_info(self):
        super().display_info()
        print(f"ボーナス: {self.calculate_bonus()}円")

class Developer(Employee):
    def __init__(self, name, salary, projects_completed):
        super().__init__(name, salary)
        self.projects_completed = projects_completed

    def calculate_bonus(self):
        return self.salary * 0.2 + self.projects_completed * 5000

    def display_info(self):
        super().display_info()
        print(f"完了プロジェクト: {self.projects_completed}")
        print(f"ボーナス: {self.calculate_bonus()}円")

# 使用例
employees = [
    Manager("山田部長", 800000),
    Developer("佐藤エンジニア", 600000, 5)
]

for employee in employees:
    employee.display_info()
    print()

問題5 解答

class BankAccount:
    def __init__(self, account_number, balance=0):
        self.account_number = account_number
        self.balance = balance

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            print(f"{amount}円預け入れました")
        return self.balance

    def withdraw(self, amount):
        if 0 < amount <= self.balance:
            self.balance -= amount
            print(f"{amount}円引き出しました")
        return self.balance

    def display_balance(self):
        print(f"口座番号: {self.account_number}")
        print(f"残高: {self.balance}円")

class SavingsAccount(BankAccount):
    def __init__(self, account_number, balance=0, interest_rate=0.02):
        super().__init__(account_number, balance)
        self.interest_rate = interest_rate

    def calculate_interest(self, months=1):
        interest = self.balance * self.interest_rate * months / 12
        print(f"{months}ヶ月分の利息: {interest:.2f}円")
        return interest

    def apply_interest(self, months=1):
        interest = self.calculate_interest(months)
        self.balance += interest
        print(f"利息を適用しました")

class CheckingAccount(BankAccount):
    def __init__(self, account_number, balance=0, transaction_fee=100):
        super().__init__(account_number, balance)
        self.transaction_fee = transaction_fee
        self.transaction_count = 0

    def withdraw(self, amount):
        result = super().withdraw(amount)
        self.transaction_count += 1
        if self.transaction_count > 3:
            self.balance -= self.transaction_fee
            print(f"取引手数料: {self.transaction_fee}円")
        return result

    def reset_transaction_count(self):
        self.transaction_count = 0
        print("取引回数をリセットしました")

# 使用例
savings = SavingsAccount("SAV001", 100000, 0.03)
checking = CheckingAccount("CHK001", 50000, 150)

print("=== 普通預金口座 ===")
savings.deposit(50000)
savings.calculate_interest(6)
savings.apply_interest()
savings.display_balance()

print("\n=== 当座預金口座 ===")
for _ in range(5):
    checking.withdraw(1000)
checking.display_balance()

問題6 解答

from abc import ABC, abstractmethod

class Item(ABC):
    def __init__(self, name, value):
        self.name = name
        self.value = value

    @abstractmethod
    def use(self, character):
        pass

    def __str__(self):
        return f"{self.name} (価値: {self.value})"

class Weapon(Item):
    def __init__(self, name, value, attack_power):
        super().__init__(name, value)
        self.attack_power = attack_power

    def use(self, character):
        print(f"{self.name}で攻撃! {self.attack_power}のダメージ!")
        return self.attack_power

class Armor(Item):
    def __init__(self, name, value, defense_power):
        super().__init__(name, value)
        self.defense_power = defense_power

    def use(self, character):
        print(f"{self.name}を装備! 防御力+{self.defense_power}")
        return self.defense_power

class Potion(Item):
    def __init__(self, name, value, heal_amount):
        super().__init__(name, value)
        self.heal_amount = heal_amount

    def use(self, character):
        print(f"{self.name}を使用! HPを{self.heal_amount}回復!")
        return self.heal_amount

class GameCharacter:
    def __init__(self, name, health=100):
        self.name = name
        self.health = health
        self.inventory = []

    def use_item(self, item_index):
        if 0 <= item_index < len(self.inventory):
            item = self.inventory[item_index]
            result = item.use(self)
            if isinstance(item, Potion):
                self.health += result
            return result
        return 0

    def add_item(self, item):
        self.inventory.append(item)
        print(f"{item.name}を入手しました")

# 使用例
character = GameCharacter("勇者")

# アイテムの追加
character.add_item(Weapon("鋼の剣", 100, 15))
character.add_item(Armor("鉄の鎧", 150, 10))
character.add_item(Potion("回復薬", 50, 30))

# ポリモーフィズムの実演
print("=== アイテム使用 ===")
for i in range(len(character.inventory)):
    character.use_item(i)
    print()

問題7 解答

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        print(f"私は{self.name}です。{self.age}歳です。")

class Student(Person):
    def __init__(self, name, age, student_id):
        super().__init__(name, age)
        self.student_id = student_id
        self.courses = []

    def enroll_course(self, course):
        self.courses.append(course)
        print(f"{course}を履修登録しました")

    def introduce(self):
        super().introduce()
        print(f"学籍番号: {self.student_id}")

class Teacher(Person):
    def __init__(self, name, age, subject):
        super().__init__(name, age)
        self.subject = subject

    def teach(self):
        print(f"{self.subject}を教えています")

    def introduce(self):
        super().introduce()
        print(f"担当科目: {self.subject}")

class Researcher:
    def __init__(self, research_field):
        self.research_field = research_field
        self.publications = []

    def add_publication(self, publication):
        self.publications.append(publication)
        print(f"論文 '{publication}' を追加しました")

    def conduct_research(self):
        print(f"{self.research_field}の研究を行っています")

class GraduateStudent(Student, Researcher):
    def __init__(self, name, age, student_id, research_field):
        Student.__init__(self, name, age, student_id)
        Researcher.__init__(self, research_field)

    def introduce(self):
        super().introduce()
        print(f"研究分野: {self.research_field}")
        print(f"論文数: {len(self.publications)}")

# 使用例
grad_student = GraduateStudent("鈴木花子", 25, "G12345", "人工知能")
grad_student.introduce()
grad_student.enroll_course("機械学習")
grad_student.add_publication("深層学習の応用")
grad_student.conduct_research()

問題8 解答

from abc import ABC, abstractmethod

class Animal(ABC):
    def __init__(self, name, habitat):
        self.name = name
        self.habitat = habitat

    @abstractmethod
    def move(self):
        pass

    def display_info(self):
        print(f"{self.name} - 生息地: {self.habitat}")

class Mammal(Animal):
    def __init__(self, name, habitat, fur_color):
        super().__init__(name, habitat)
        self.fur_color = fur_color

    def move(self):
        print(f"{self.name}は走ります")

    def display_info(self):
        super().display_info()
        print(f"毛色: {self.fur_color}")

class Bird(Animal):
    def __init__(self, name, habitat, wingspan):
        super().__init__(name, habitat)
        self.wingspan = wingspan

    def move(self):
        print(f"{self.name}は飛びます")

    def display_info(self):
        super().display_info()
        print(f"翼幅: {self.wingspan}cm")

class Reptile(Animal):
    def __init__(self, name, habitat, scale_type):
        super().__init__(name, habitat)
        self.scale_type = scale_type

    def move(self):
        print(f"{self.name}は這います")

    def display_info(self):
        super().display_info()
        print(f"鱗の種類: {self.scale_type}")

# 哺乳類のサブクラス
class Lion(Mammal):
    def __init__(self, name, habitat, fur_color, mane_size):
        super().__init__(name, habitat, fur_color)
        self.mane_size = mane_size

    def move(self):
        print(f"{self.name}は悠然と歩きます")

    def roar(self):
        print("ガオー!")

# 鳥類のサブクラス
class Eagle(Bird):
    def __init__(self, name, habitat, wingspan, vision_range):
        super().__init__(name, habitat, wingspan)
        self.vision_range = vision_range

    def move(self):
        print(f"{self.name}は高く舞い上がります")

    def hunt(self):
        print("獲物を探しています")

# 爬虫類のサブクラス
class Snake(Reptile):
    def __init__(self, name, habitat, scale_type, length):
        super().__init__(name, habitat, scale_type)
        self.length = length

    def move(self):
        print(f"{self.name}は静かに這います")

    def shed_skin(self):
        print("脱皮しました")

# 使用例
animals = [
    Lion("シンバ", "サバンナ", "金色", "大きい"),
    Eagle("ホーク", "山岳地帯", 200, "2km"),
    Snake("ナーガ", "ジャングル", "光沢のある", 3.0)
]

for animal in animals:
    animal.display_info()
    animal.move()
    print()

問題9 解答

class ElectronicDevice:
    def __init__(self, brand, model, power_consumption):
        self.brand = brand
        self.model = model
        self.power_consumption = power_consumption
        self.is_on = False

    def turn_on(self):
        self.is_on = True
        print(f"{self.brand} {self.model}の電源を入れました")

    def turn_off(self):
        self.is_on = False
        print(f"{self.brand} {self.model}の電源を切りました")

    def get_power_usage(self, hours):
        return self.power_consumption * hours

    def display_info(self):
        status = "ON" if self.is_on else "OFF"
        print(f"{self.brand} {self.model} - 状態: {status}")

class SmartPhone(ElectronicDevice):
    def __init__(self, brand, model, power_consumption, os, screen_size):
        super().__init__(brand, model, power_consumption)
        self.os = os
        self.screen_size = screen_size
        self.apps = []

    def make_call(self, number):
        if self.is_on:
            print(f"{number}に電話をかけます")
        else:
            print("電源が入っていません")

    def install_app(self, app_name):
        self.apps.append(app_name)
        print(f"アプリ '{app_name}' をインストールしました")

    def display_info(self):
        super().display_info()
        print(f"OS: {self.os}, 画面サイズ: {self.screen_size}インチ")
        print(f"インストールアプリ数: {len(self.apps)}")

class Laptop(ElectronicDevice):
    def __init__(self, brand, model, power_consumption, processor, ram):
        super().__init__(brand, model, power_consumption)
        self.processor = processor
        self.ram = ram
        self.battery_level = 100

    def code(self):
        if self.is_on:
            print("コーディング中...")
            self.battery_level -= 5
        else:
            print("電源が入っていません")

    def check_battery(self):
        print(f"バッテリー残量: {self.battery_level}%")

    def display_info(self):
        super().display_info()
        print(f"プロセッサ: {self.processor}, RAM: {self.ram}GB")

class Tablet(ElectronicDevice):
    def __init__(self, brand, model, power_consumption, stylus_support, storage):
        super().__init__(brand, model, power_consumption)
        self.stylus_support = stylus_support
        self.storage = storage
        self.drawing_apps = []

    def draw(self):
        if self.is_on and self.stylus_support:
            print("絵を描いています")
        elif not self.stylus_support:
            print("このタブレットはスタイラス非対応です")
        else:
            print("電源が入っていません")

    def install_drawing_app(self, app_name):
        self.drawing_apps.append(app_name)
        print(f"描画アプリ '{app_name}' をインストールしました")

    def display_info(self):
        super().display_info()
        support = "対応" if self.stylus_support else "非対応"
        print(f"スタイラス: {support}, ストレージ: {self.storage}GB")

# 使用例
devices = [
    SmartPhone("Apple", "iPhone 15", 5, "iOS", 6.1),
    Laptop("Dell", "XPS 13", 45, "Intel i7", 16),
    Tablet("Samsung", "Galaxy Tab", 8, True, 128)
]

for device in devices:
    device.turn_on()
    device.display_info()

    # デバイス特有の機能
    if isinstance(device, SmartPhone):
        device.make_call("090-1234-5678")
        device.install_app("LINE")
    elif isinstance(device, Laptop):
        device.code()
        device.check_battery()
    elif isinstance(device, Tablet):
        device.draw()
        device.install_drawing_app("Procreate")

    print()

上級問題

問題10 解答

from abc import ABC, abstractmethod
import math

class Drawable(ABC):
    @abstractmethod
    def draw(self):
        pass

    @abstractmethod
    def get_area(self):
        pass

    @abstractmethod
    def get_perimeter(self):
        pass

class Point(Drawable):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def draw(self):
        print(f"点を描画: ({self.x}, {self.y})")

    def get_area(self):
        return 0

    def get_perimeter(self):
        return 0

    def distance_to(self, other_point):
        return math.sqrt((self.x - other_point.x)**2 + (self.y - other_point.y)**2)

class Line(Drawable):
    def __init__(self, start_point, end_point):
        self.start_point = start_point
        self.end_point = end_point

    def draw(self):
        print(f"線を描画: ({self.start_point.x}, {self.start_point.y}) -> ({self.end_point.x}, {self.end_point.y})")

    def get_area(self):
        return 0

    def get_perimeter(self):
        return self.start_point.distance_to(self.end_point)

    def get_length(self):
        return self.get_perimeter()

class Rectangle(Drawable):
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

    def draw(self):
        print(f"長方形を描画: 位置({self.x}, {self.y}), サイズ({self.width}×{self.height})")

    def get_area(self):
        return self.width * self.height

    def get_perimeter(self):
        return 2 * (self.width + self.height)

class Circle(Drawable):
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius

    def draw(self):
        print(f"円を描画: 中心({self.x}, {self.y}), 半径{self.radius}")

    def get_area(self):
        return math.pi * self.radius ** 2

    def get_perimeter(self):
        return 2 * math.pi * self.radius

class Drawing:
    def __init__(self):
        self.shapes = []

    def add_shape(self, shape):
        self.shapes.append(shape)
        print("図形を追加しました")

    def draw_all(self):
        print("=== すべての図形を描画 ===")
        for shape in self.shapes:
            shape.draw()
            print(f"  面積: {shape.get_area():.2f}, 周囲長: {shape.get_perimeter():.2f}")
        print("========================")

    def total_area(self):
        return sum(shape.get_area() for shape in self.shapes)

    def total_perimeter(self):
        return sum(shape.get_perimeter() for shape in self.shapes)

# 使用例
drawing = Drawing()

# 様々な図形を追加
drawing.add_shape(Point(0, 0))
drawing.add_shape(Line(Point(0, 0), Point(3, 4)))
drawing.add_shape(Rectangle(1, 1, 5, 3))
drawing.add_shape(Circle(2, 2, 3))

# ポリモーフィズムで一括描画
drawing.draw_all()

print(f"総面積: {drawing.total_area():.2f}")
print(f"総周囲長: {drawing.total_perimeter():.2f}")

問題11 解答

from abc import ABC, abstractmethod
from datetime import datetime

class Product(ABC):
    def __init__(self, product_id, name, price):
        self.product_id = product_id
        self.name = name
        self.price = price
        self.created_at = datetime.now()

    @abstractmethod
    def check_inventory(self):
        pass

    @abstractmethod
    def update_inventory(self, quantity):
        pass

    @abstractmethod
    def can_sell(self, quantity):
        pass

    def display_info(self):
        print(f"ID: {self.product_id}")
        print(f"商品名: {self.name}")
        print(f"価格: {self.price}円")

class PhysicalProduct(Product):
    def __init__(self, product_id, name, price, stock_quantity, weight):
        super().__init__(product_id, name, price)
        self.stock_quantity = stock_quantity
        self.weight = weight

    def check_inventory(self):
        return self.stock_quantity

    def update_inventory(self, quantity):
        if self.stock_quantity + quantity >= 0:
            self.stock_quantity += quantity
            return True
        return False

    def can_sell(self, quantity):
        return self.stock_quantity >= quantity

    def calculate_shipping_cost(self, distance):
        return self.weight * distance * 0.1

    def display_info(self):
        super().display_info()
        print(f"在庫数: {self.stock_quantity}")
        print(f"重量: {self.weight}kg")

class DigitalProduct(Product):
    def __init__(self, product_id, name, price, file_size, download_limit=None):
        super().__init__(product_id, name, price)
        self.file_size = file_size
        self.download_limit = download_limit
        self.download_count = 0

    def check_inventory(self):
        if self.download_limit is None:
            return "無制限"
        return f"{self.download_limit - self.download_count}回"

    def update_inventory(self, quantity):
        if self.download_limit is None:
            return True

        if self.download_count + quantity <= self.download_limit:
            self.download_count += quantity
            return True
        return False

    def can_sell(self, quantity):
        if self.download_limit is None:
            return True
        return self.download_count + quantity <= self.download_limit

    def get_file_info(self):
        return f"ファイルサイズ: {self.file_size}MB, ダウンロード回数: {self.download_count}"

    def display_info(self):
        super().display_info()
        print(f"ファイルサイズ: {self.file_size}MB")
        if self.download_limit:
            print(f"ダウンロード制限: {self.download_limit}回")
        print(f"現在のダウンロード回数: {self.download_count}")

class InventoryManager:
    def __init__(self):
        self.products = []

    def add_product(self, product):
        self.products.append(product)
        print(f"商品 '{product.name}' を追加しました")

    def sell_product(self, product_id, quantity):
        for product in self.products:
            if product.product_id == product_id and product.can_sell(quantity):
                product.update_inventory(quantity)
                total_price = product.price * quantity
                print(f"商品 '{product.name}' {quantity}個を販売しました")
                print(f"売上: {total_price}円")

                # 物理商品の場合、配送コストを計算
                if isinstance(product, PhysicalProduct):
                    shipping_cost = product.calculate_shipping_cost(100)  # 仮の距離
                    print(f"配送コスト: {shipping_cost}円")

                return True
        print("販売できませんでした")
        return False

    def display_inventory_report(self):
        print("\n=== 在庫レポート ===")
        for product in self.products:
            product.display_info()
            print(f"在庫状態: {product.check_inventory()}")
            print("---")
        print("===================")

# 使用例
inventory_manager = InventoryManager()

# 商品の追加
physical_product = PhysicalProduct("P001", "ノートパソコン", 150000, 10, 2.5)
digital_product = DigitalProduct("D001", "電子書籍", 2000, 50, 3)

inventory_manager.add_product(physical_product)
inventory_manager.add_product(digital_product)

# 在庫レポート表示
inventory_manager.display_inventory_report()

# 商品販売
print("\n=== 販売処理 ===")
inventory_manager.sell_product("P001", 2)
inventory_manager.sell_product("D001", 1)

# 最終レポート
inventory_manager.display_inventory_report()

問題12 解答

from abc import ABC, abstractmethod
from datetime import datetime

class Notification(ABC):
    def __init__(self, recipient, message):
        self.recipient = recipient
        self.message = message
        self.sent_at = None
        self.status = "準備中"

    @abstractmethod
    def send(self):
        pass

    @abstractmethod
    def get_notification_type(self):
        pass

    def mark_sent(self):
        self.sent_at = datetime.now()
        self.status = "送信済み"

    def get_status(self):
        return f"{self.get_notification_type()} - {self.status}"

    def display_info(self):
        print(f"通知タイプ: {self.get_notification_type()}")
        print(f"受信者: {self.recipient}")
        print(f"メッセージ: {self.message}")
        print(f"ステータス: {self.status}")
        if self.sent_at:
            print(f"送信日時: {self.sent_at.strftime('%Y-%m-%d %H:%M:%S')}")

class EmailNotification(Notification):
    def __init__(self, recipient, message, subject, from_address):
        super().__init__(recipient, message)
        self.subject = subject
        self.from_address = from_address

    def send(self):
        print(f"メール送信: {self.from_address} -> {self.recipient}")
        print(f"件名: {self.subject}")
        print(f"本文: {self.message}")
        self.mark_sent()
        return True

    def get_notification_type(self):
        return "メール通知"

    def display_info(self):
        super().display_info()
        print(f"差出人: {self.from_address}")
        print(f"件名: {self.subject}")

class SMSNotification(Notification):
    def __init__(self, recipient, message, phone_number):
        super().__init__(recipient, message)
        self.phone_number = phone_number

    def send(self):
        print(f"SMS送信: {self.phone_number}")
        print(f"メッセージ: {self.message}")
        self.mark_sent()
        return True

    def get_notification_type(self):
        return "SMS通知"

    def display_info(self):
        super().display_info()
        print(f"電話番号: {self.phone_number}")

class PushNotification(Notification):
    def __init__(self, recipient, message, device_token, app_name):
        super().__init__(recipient, message)
        self.device_token = device_token
        self.app_name = app_name

    def send(self):
        print(f"プッシュ通知送信: {self.app_name}")
        print(f"デバイス: {self.device_token}")
        print(f"メッセージ: {self.message}")
        self.mark_sent()
        return True

    def get_notification_type(self):
        return "プッシュ通知"

    def display_info(self):
        super().display_info()
        print(f"アプリ名: {self.app_name}")
        print(f"デバイストークン: {self.device_token}")

class NotificationManager:
    def __init__(self):
        self.notifications = []
        self.sent_count = 0

    def add_notification(self, notification):
        self.notifications.append(notification)
        print(f"通知を追加しました: {notification.get_notification_type()}")

    def send_all(self):
        print("\n=== 一括通知送信 ===")
        success_count = 0
        for notification in self.notifications:
            if notification.status != "送信済み":
                if notification.send():
                    success_count += 1
                print("---")
        self.sent_count += success_count
        print(f"{success_count}件の通知を送信しました")

    def send_by_type(self, notification_type):
        print(f"\n=== {notification_type}のみ送信 ===")
        count = 0
        for notification in self.notifications:
            if (notification.get_notification_type() == notification_type and 
                notification.status != "送信済み"):
                if notification.send():
                    count += 1
                print("---")
        print(f"{count}件の{notification_type}を送信しました")

    def display_statistics(self):
        print("\n=== 通知統計 ===")
        type_count = {}
        for notification in self.notifications:
            n_type = notification.get_notification_type()
            type_count[n_type] = type_count.get(n_type, 0) + 1

        for n_type, count in type_count.items():
            print(f"{n_type}: {count}件")
        print(f"総送信済み数: {self.sent_count}件")
        print("================")

    def display_all_notifications(self):
        print("\n=== すべての通知 ===")
        for i, notification in enumerate(self.notifications, 1):
            print(f"{i}. ", end="")
            notification.display_info()
            print()

# 使用例
notification_manager = NotificationManager()

# 様々なタイプの通知を作成
email_notif = EmailNotification(
    "user@example.com", 
    "お知らせがあります", 
    "重要なお知らせ", 
    "noreply@company.com"
)

sms_notif = SMSNotification(
    "山田太郎", 
    "本日15時にお伺いします", 
    "090-1234-5678"
)

push_notif = PushNotification(
    "user123", 
    "新しいメッセージが届きました", 
    "device_token_abc123", 
    "MyApp"
)

# 通知をマネージャーに追加
notification_manager.add_notification(email_notif)
notification_manager.add_notification(sms_notif)
notification_manager.add_notification(push_notif)

# 通知の表示
notification_manager.display_all_notifications()

# 統計表示
notification_manager.display_statistics()

# 通知送信
notification_manager.send_all()

# 最終統計
notification_manager.display_statistics()