基礎から学ぶPython入門 90日コース | 実践Python - Day 24:オブジェクト指向実践

90日で身につけるPython
スポンサーリンク
スポンサーリンク

Day 24:オブジェクト指向を「実践レベル」に一歩進める

Day 24では、オブジェクト指向の実践的な要素である 継承・オーバーライド・@property・カプセル化 を学びます。 「クラスをただ作る」から、「クラス同士の関係を設計する」段階に進むイメージで進めていきます。

継承:共通部分を「親クラス」にまとめる

継承とは何か

継承(inheritance) は、

  • 共通する性質や振る舞いを「親クラス」にまとめて
  • それを「子クラス」が引き継ぐ

という仕組みです。

イメージとしては、

  • 「ユーザー」という親クラス
  • 「管理者ユーザー」「一般ユーザー」という子クラス

のように、「共通部分+追加の特徴」を表現するためのものです。

基本的な継承の書き方

まずは、親クラス User を定義します。

class User:
    """ユーザーの基本的なクラスです。"""

    def __init__(self, name, email):
        self.name = name
        self.email = email

    def introduce(self):
        """自己紹介を表示します。"""
        print(f"こんにちは、{self.name}です。メールアドレスは {self.email} です。")
Python

この User を継承した「管理者ユーザー」を作ってみます。

class AdminUser(User):
    """管理者ユーザーを表すクラスです。Userを継承しています。"""

    def __init__(self, name, email, level):
        # 親クラス(User)の初期化を呼び出します。
        super().__init__(name, email)
        # AdminUser独自の属性を追加します。
        self.level = level

    def show_admin_info(self):
        """管理者としての情報を表示します。"""
        print(f"管理者レベル: {self.level}")
Python

ここでの重要ポイントは、

  • class AdminUser(User): と書くことで、「Userを継承したAdminUser」を定義していること
  • super().__init__(name, email) で「親クラスの初期化処理」を呼び出していること
  • 子クラスに「独自の属性」や「独自のメソッド」を追加できること

です。

継承したクラスを使ってみる

admin = AdminUser("Alice", "alice@example.com", level=10)

admin.introduce()       # 親クラスのメソッドが使えます
admin.show_admin_info() # 子クラス独自のメソッドも使えます
Python

継承によって、

  • 共通部分(名前・メールアドレス・自己紹介)は親クラスにまとめ
  • 管理者としての追加情報(レベル)や振る舞いは子クラスに追加する

という構造が自然に表現できます。

オーバーライド:親クラスの振る舞いを「上書き」する

オーバーライドとは何か

オーバーライド(override) は、

  • 親クラスに定義されているメソッドを
  • 子クラスで「書き換える(上書きする)」

ことです。

例えば、「管理者ユーザーは自己紹介の仕方を少し変えたい」という場合です。

メソッドをオーバーライドする

先ほどの User クラスを少し使い回して、AdminUserintroduce() をオーバーライドしてみます。

class User:
    """ユーザーの基本的なクラスです。"""

    def __init__(self, name, email):
        self.name = name
        self.email = email

    def introduce(self):
        """自己紹介を表示します。"""
        print(f"こんにちは、{self.name}です。メールアドレスは {self.email} です。")


class AdminUser(User):
    """管理者ユーザーを表すクラスです。"""

    def __init__(self, name, email, level):
        super().__init__(name, email)
        self.level = level

    def introduce(self):
        """管理者としての自己紹介を表示します(オーバーライド)。"""
        print(f"【管理者】{self.name}(レベル: {self.level})です。連絡先は {self.email} です。")
Python

使ってみます。

user = User("Bob", "bob@example.com")
admin = AdminUser("Alice", "alice@example.com", level=10)

user.introduce()
admin.introduce()
Python

出力イメージ:

こんにちは、Bobです。メールアドレスは bob@example.com です。
【管理者】Alice(レベル: 10)です。連絡先は alice@example.com です。

ここでの重要ポイントは、

  • 同じメソッド名 introduce を子クラスで定義し直すことで、「振る舞いを変えている」こと
  • 呼び出し側は admin.introduce() と書くだけで、「管理者用の自己紹介」が実行されること

です。これにより、「共通のインターフェースを保ちつつ、振る舞いだけ変える」ことができます。

@property:メソッドを「属性っぽく」見せる

なぜ@propertyが必要なのか

オブジェクト指向では、「内部の計算結果」を外から「属性のように」扱いたい場面があります。

例えば、「フルネーム」を考えてみます。

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
Python

この人の「フルネーム」は、first_name + " " + last_name で作れますが、 毎回 person.first_name + " " + person.last_name と書くのは少し面倒です。

そこで、@property を使って「フルネーム」を属性のように扱えるようにします。

@propertyの基本的な使い方

class Person:
    """人を表すクラスです。"""

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def full_name(self):
        """フルネームを返すプロパティです。"""
        return f"{self.first_name} {self.last_name}"
Python

使ってみます。

person = Person("Alice", "Smith")

print(person.full_name)  # メソッド呼び出しではなく「属性アクセス」の形で使えます
Python

ここでの重要ポイントは、

  • @property を付けたメソッドは、「属性のように」アクセスできること
  • person.full_name と書いているのに、内部ではメソッドが呼ばれていること
  • 「計算が必要な値」を、自然な形で扱えるようになること

です。

setterを使って「書き込み」も制御する

@property は「読み取り専用」にもできますが、 @full_name.setter を使うと「書き込み時の処理」も定義できます。

class Person:
    """フルネームをプロパティで扱う例です。"""

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def full_name(self):
        """フルネームを返します。"""
        return f"{self.first_name} {self.last_name}"

    @full_name.setter
    def full_name(self, value):
        """フルネームを 'First Last' 形式で受け取り、first_name と last_name に分割します。"""
        parts = value.split(" ", 1)
        if len(parts) != 2:
            raise ValueError("フルネームは 'First Last' 形式で指定してください。")
        self.first_name, self.last_name = parts
Python

使ってみます。

person = Person("Alice", "Smith")
print(person.full_name)  # Alice Smith

person.full_name = "Bob Johnson"  # setterが呼ばれます
print(person.first_name)  # Bob
print(person.last_name)   # Johnson
Python

ここでのポイントは、

  • full_name という「1つのプロパティ」を通して、内部の2つの属性をまとめて扱っていること
  • setterの中で「入力値の形式チェック」を行えること

です。これが、次に説明する「カプセル化」ともつながっていきます。

カプセル化:内部の状態を「守る」設計

カプセル化とは何か

カプセル化(encapsulation) は、

  • オブジェクトの内部状態(属性)を
  • 外部から直接いじられないように守り
  • 必要な操作だけをメソッドやプロパティとして公開する

という考え方です。

Pythonでは「完全な隠蔽」はありませんが、 慣習として「外から直接触ってほしくない属性」を ___ で始めることで表現します。

「直接いじってほしくない属性」を隠す

例えば、「銀行口座」を表現するクラスを考えます。

class BankAccount:
    """銀行口座を表すクラスです。"""

    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance  # 外から直接いじってほしくないので、先頭に _ を付けています。

    @property
    def balance(self):
        """残高を読み取るためのプロパティです。"""
        return self._balance

    def deposit(self, amount):
        """入金処理を行います。"""
        if amount <= 0:
            raise ValueError("入金額は正の数である必要があります。")
        self._balance += amount

    def withdraw(self, amount):
        """出金処理を行います。"""
        if amount <= 0:
            raise ValueError("出金額は正の数である必要があります。")
        if amount > self._balance:
            raise ValueError("残高不足です。")
        self._balance -= amount
Python

使ってみます。

account = BankAccount("Alice", balance=1000)

print("残高:", account.balance)  # プロパティ経由で残高を参照します。

account.deposit(500)
print("入金後の残高:", account.balance)

account.withdraw(300)
print("出金後の残高:", account.balance)
Python

ここでの重要ポイントは、

  • _balance を「外から直接いじってほしくない内部属性」として扱っていること
  • 残高の変更は depositwithdraw を通してのみ行うようにしていること
  • その中で「不正な操作(マイナス入金・残高不足の出金)」を防いでいること

です。これがカプセル化の本質であり、「オブジェクトの中にルールを閉じ込める」イメージです。

Day 24ミニテンプレート:オブジェクト指向実践スクリプト

最後に、Day 24で学んだ内容をまとめて試せる「オブジェクト指向実践スクリプト」のテンプレートを示します。

# day24_oop_practice.py
# オブジェクト指向実践の練習用スクリプト

class User:
    """ユーザーの基本クラスです。"""

    def __init__(self, name, email):
        self.name = name
        self.email = email

    def introduce(self):
        """自己紹介を表示します。"""
        print(f"こんにちは、{self.name}です。メールアドレスは {self.email} です。")


class AdminUser(User):
    """管理者ユーザークラスです。Userを継承しています。"""

    def __init__(self, name, email, level):
        super().__init__(name, email)
        self.level = level

    def introduce(self):
        """管理者としての自己紹介(オーバーライド)。"""
        print(f"【管理者】{self.name}(レベル: {self.level})です。連絡先は {self.email} です。")


class Person:
    """@property の例としての人クラスです。"""

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def full_name(self):
        """フルネームを返すプロパティです。"""
        return f"{self.first_name} {self.last_name}"

    @full_name.setter
    def full_name(self, value):
        """フルネームを 'First Last' 形式で受け取り、分割します。"""
        parts = value.split(" ", 1)
        if len(parts) != 2:
            raise ValueError("フルネームは 'First Last' 形式で指定してください。")
        self.first_name, self.last_name = parts


class BankAccount:
    """カプセル化の例としての銀行口座クラスです。"""

    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance  # 内部属性(直接いじってほしくない)

    @property
    def balance(self):
        """残高を読み取るためのプロパティです。"""
        return self._balance

    def deposit(self, amount):
        """入金処理を行います。"""
        if amount <= 0:
            raise ValueError("入金額は正の数である必要があります。")
        self._balance += amount

    def withdraw(self, amount):
        """出金処理を行います。"""
        if amount <= 0:
            raise ValueError("出金額は正の数である必要があります。")
        if amount > self._balance:
            raise ValueError("残高不足です。")
        self._balance -= amount


def main():
    print("=== 継承とオーバーライドの例 ===")
    user = User("Bob", "bob@example.com")
    admin = AdminUser("Alice", "alice@example.com", level=10)
    user.introduce()
    admin.introduce()

    print("\n=== @property の例 ===")
    person = Person("Charlie", "Brown")
    print("フルネーム:", person.full_name)
    person.full_name = "Dave Smith"
    print("変更後のフルネーム:", person.full_name)

    print("\n=== カプセル化の例 ===")
    account = BankAccount("Eve", balance=1000)
    print("初期残高:", account.balance)
    account.deposit(500)
    print("入金後の残高:", account.balance)
    account.withdraw(300)
    print("出金後の残高:", account.balance)


if __name__ == "__main__":
    main()
Python

Day 24のまとめ

Day 24では、オブジェクト指向実践として、

  • 継承で「共通部分を親クラスにまとめ、子クラスに特徴を追加する」設計
  • オーバーライドで「同じメソッド名のまま、振る舞いだけを変える」テクニック
  • @property で「計算が必要な値を属性のように扱う」書き方
  • カプセル化で「内部状態を守り、ルールをオブジェクトの中に閉じ込める」考え方

をステップバイステップで体験していただきました。

ここまで来ると、「クラス単体」ではなく、「クラス同士の関係」や「クラスの中に閉じ込めるルール」を意識して設計する感覚が少し育っているはずです。 この感覚は、今後の実践Python編で、より複雑なアプリケーションやドメインを扱うときに、コードを整理し続けるための強力な武器になっていきます。


Day 24:オブジェクト指向実践で「商品管理システム」を作る

Day 24の制作編では、 継承・オーバーライド・@property・カプセル化 をすべて盛り込んだ「商品管理システム」を作っていきます。 単なるクラスの練習ではなく、「現実のドメイン(商品管理)」をコードでどう表現するかを体験していただくことを目標にします。

商品管理システムの全体像をイメージする

どんなクラスを作るか

今回の商品管理システムでは、次のような構成を目指します。

  • 基底クラスProduct
    • すべての商品に共通する「名前」「価格」「在庫数」を持つ
    • 在庫の増減メソッドを持つ
  • 派生クラスPhysicalProduct(物理商品)
    • 配送が必要な商品(本・PC・家具など)
    • 送料や在庫の扱いを少し変える
  • 派生クラスDigitalProduct(デジタル商品)
    • ダウンロード商品(電子書籍・ソフトウェアなど)
    • 在庫の概念を変える(在庫無限など)
  • @property を使った「税込価格」や「在庫価値」の計算
  • カプセル化された在庫管理(不正な在庫操作を防ぐ)

これらを組み合わせて、「商品を登録し、一覧表示し、在庫を操作する」簡易システムを作ります。

基底クラス Product を設計する

共通する属性とメソッドをまとめる

まずは、すべての商品に共通する部分を Product クラスにまとめます。

class Product:
    """商品を表す基底クラスです。"""

    def __init__(self, name: str, price: int, stock: int = 0):
        # 商品名
        self.name = name

        # 価格(税抜き)
        self._price = price  # 外から直接いじってほしくないので、先頭に _ を付けています。

        # 在庫数
        self._stock = stock  # これもカプセル化の対象です。

    @property
    def price(self) -> int:
        """税抜き価格を返すプロパティです。"""
        return self._price

    @price.setter
    def price(self, value: int):
        """価格を設定する際のチェックを行うsetterです。"""
        if value < 0:
            raise ValueError("価格は0以上である必要があります。")
        self._price = value

    @property
    def stock(self) -> int:
        """在庫数を返すプロパティです。"""
        return self._stock

    def add_stock(self, amount: int):
        """在庫を増やします。"""
        if amount <= 0:
            raise ValueError("追加する在庫数は正の数である必要があります。")
        self._stock += amount

    def remove_stock(self, amount: int):
        """在庫を減らします。"""
        if amount <= 0:
            raise ValueError("減らす在庫数は正の数である必要があります。")
        if amount > self._stock:
            raise ValueError("在庫不足です。")
        self._stock -= amount

    @property
    def total_value(self) -> int:
        """在庫の総額(税抜き)を返すプロパティです。"""
        return self.price * self.stock

    def show_info(self):
        """商品情報を表示するメソッドです。"""
        print(f"商品名: {self.name}")
        print(f"価格(税抜): {self.price} 円")
        print(f"在庫数: {self.stock} 個")
        print(f"在庫総額(税抜): {self.total_value} 円")
Python

ここでの重要ポイントは次の通りです。

  • カプセル化
    • _price_stock を「内部属性」として扱い、 pricestock プロパティ経由でアクセスするようにしています。
    • setterで「価格は0以上」「在庫操作は正の数のみ」「在庫不足を防ぐ」といったルールを閉じ込めています。
  • @property
    • pricestocktotal_value をプロパティにすることで、 「計算が必要な値」も自然な形で扱えるようにしています。
  • 共通メソッド
    • add_stockremove_stockshow_info は、すべての商品に共通する振る舞いです。

この Product が、物理商品・デジタル商品の「親クラス」になります。

物理商品クラス PhysicalProduct を継承で作る

送料や税込価格を追加する

物理商品は、「送料」や「税込価格」を意識することが多いです。 そこで、Product を継承した PhysicalProduct クラスを作ります。

class PhysicalProduct(Product):
    """物理商品を表すクラスです。Productを継承しています。"""

    def __init__(self, name: str, price: int, stock: int = 0, shipping_fee: int = 0, tax_rate: float = 0.1):
        # 親クラス(Product)の初期化を呼び出します。
        super().__init__(name, price, stock)
        # 物理商品独自の属性を追加します。
        self.shipping_fee = shipping_fee  # 送料(1回の注文あたり)
        self.tax_rate = tax_rate          # 消費税率(例: 0.1 = 10%)

    @property
    def price_with_tax(self) -> int:
        """税込価格を返すプロパティです。"""
        return int(self.price * (1 + self.tax_rate))

    def show_info(self):
        """商品情報を表示するメソッド(オーバーライド)。"""
        print("=== 物理商品 ===")
        print(f"商品名: {self.name}")
        print(f"価格(税抜): {self.price} 円")
        print(f"価格(税込): {self.price_with_tax} 円")
        print(f"送料: {self.shipping_fee} 円")
        print(f"在庫数: {self.stock} 個")
        print(f"在庫総額(税抜): {self.total_value} 円")
Python

ここでの重要ポイントは次の通りです。

  • 継承
    • class PhysicalProduct(Product): と書くことで、Product のすべての機能を引き継いでいます。
  • オーバーライド
    • show_info を子クラスで書き直すことで、「物理商品用の表示」に変えています。
  • @property
    • price_with_tax をプロパティにすることで、「税込価格」を自然な形で扱えるようにしています。

デジタル商品クラス DigitalProduct を継承で作る

在庫の概念を変える(在庫無限など)

デジタル商品(電子書籍・ソフトウェアなど)は、 「在庫切れ」という概念がないことが多いです。 そこで、Product を継承しつつ、在庫の扱いをオーバーライドしてみます。

class DigitalProduct(Product):
    """デジタル商品を表すクラスです。Productを継承しています。"""

    def __init__(self, name: str, price: int):
        # デジタル商品は在庫無限とみなすので、stockは0固定で親を初期化します。
        super().__init__(name, price, stock=0)

    @property
    def stock(self) -> int:
        """在庫数を返すプロパティです。デジタル商品は在庫無限とみなします。"""
        # 実際には「∞」のような概念ですが、ここでは説明用に -1 を「無限」の意味として返します。
        return -1

    @property
    def total_value(self) -> int:
        """在庫総額は意味がないので、0を返します。"""
        return 0

    def add_stock(self, amount: int):
        """デジタル商品の在庫は増やす必要がないので、何もしません。"""
        print("デジタル商品の在庫は管理していません。")

    def remove_stock(self, amount: int):
        """デジタル商品の在庫は減らす必要がないので、何もしません。"""
        print("デジタル商品の在庫は管理していません。")

    def show_info(self):
        """商品情報を表示するメソッド(オーバーライド)。"""
        print("=== デジタル商品 ===")
        print(f"商品名: {self.name}")
        print(f"価格(税抜): {self.price} 円")
        print("在庫: 無制限")
Python

ここでの重要ポイントは次の通りです。

  • オーバーライドで「振る舞いを変える」
    • stock プロパティ・total_valueadd_stockremove_stock をオーバーライドすることで、 「デジタル商品は在庫管理しない」というルールをクラスの中に閉じ込めています。
  • カプセル化されたルール
    • 外から見たときには「在庫無限」「在庫操作は意味がない」ということが、 クラスのメソッドを通して自然に伝わるようになっています。

商品管理システムの「管理クラス」を作る

商品一覧を管理する ProductManager

複数の商品をまとめて管理するために、ProductManager クラスを作ります。

class ProductManager:
    """商品をまとめて管理するクラスです。"""

    def __init__(self):
        self._products = []  # 商品インスタンスのリスト

    def add_product(self, product: Product):
        """商品を追加します。"""
        self._products.append(product)

    def list_products(self):
        """登録されている商品を一覧表示します。"""
        print("=== 商品一覧 ===")
        if not self._products:
            print("商品が登録されていません。")
            return

        for idx, product in enumerate(self._products, start=1):
            print(f"\n[{idx}]")
            product.show_info()

    def find_by_name(self, name: str) -> Product | None:
        """商品名で検索し、見つかった商品を返します。見つからなければNoneを返します。"""
        for product in self._products:
            if product.name == name:
                return product
        return None
Python

ここでのポイントは、

  • Product を継承した PhysicalProductDigitalProduct のインスタンスを、 「同じリスト _products にまとめて管理している」こと
  • product.show_info() を呼ぶだけで、 物理商品・デジタル商品それぞれの表示が「オーバーライドされたメソッド」によって自動的に切り替わること

です。これが「ポリモーフィズム(多態性)」の入り口でもあります。

商品管理システムの全体テンプレート

ここまでのクラスを組み合わせて、 「商品を登録し、一覧表示し、在庫を操作する」簡易システムの全体コードをまとめます。

# day24_product_system.py
# オブジェクト指向実践:商品管理システム

class Product:
    """商品を表す基底クラスです。"""

    def __init__(self, name: str, price: int, stock: int = 0):
        self.name = name
        self._price = price
        self._stock = stock

    @property
    def price(self) -> int:
        """税抜き価格を返すプロパティです。"""
        return self._price

    @price.setter
    def price(self, value: int):
        """価格を設定する際のチェックを行うsetterです。"""
        if value < 0:
            raise ValueError("価格は0以上である必要があります。")
        self._price = value

    @property
    def stock(self) -> int:
        """在庫数を返すプロパティです。"""
        return self._stock

    def add_stock(self, amount: int):
        """在庫を増やします。"""
        if amount <= 0:
            raise ValueError("追加する在庫数は正の数である必要があります。")
        self._stock += amount

    def remove_stock(self, amount: int):
        """在庫を減らします。"""
        if amount <= 0:
            raise ValueError("減らす在庫数は正の数である必要があります。")
        if amount > self._stock:
            raise ValueError("在庫不足です。")
        self._stock -= amount

    @property
    def total_value(self) -> int:
        """在庫の総額(税抜き)を返すプロパティです。"""
        return self.price * self.stock

    def show_info(self):
        """商品情報を表示するメソッドです。"""
        print("=== 商品(基底クラス) ===")
        print(f"商品名: {self.name}")
        print(f"価格(税抜): {self.price} 円")
        print(f"在庫数: {self.stock} 個")
        print(f"在庫総額(税抜): {self.total_value} 円")


class PhysicalProduct(Product):
    """物理商品を表すクラスです。Productを継承しています。"""

    def __init__(self, name: str, price: int, stock: int = 0, shipping_fee: int = 0, tax_rate: float = 0.1):
        super().__init__(name, price, stock)
        self.shipping_fee = shipping_fee
        self.tax_rate = tax_rate

    @property
    def price_with_tax(self) -> int:
        """税込価格を返すプロパティです。"""
        return int(self.price * (1 + self.tax_rate))

    def show_info(self):
        """商品情報を表示するメソッド(オーバーライド)。"""
        print("=== 物理商品 ===")
        print(f"商品名: {self.name}")
        print(f"価格(税抜): {self.price} 円")
        print(f"価格(税込): {self.price_with_tax} 円")
        print(f"送料: {self.shipping_fee} 円")
        print(f"在庫数: {self.stock} 個")
        print(f"在庫総額(税抜): {self.total_value} 円")


class DigitalProduct(Product):
    """デジタル商品を表すクラスです。Productを継承しています。"""

    def __init__(self, name: str, price: int):
        super().__init__(name, price, stock=0)

    @property
    def stock(self) -> int:
        """在庫数を返すプロパティです。デジタル商品は在庫無限とみなします。"""
        return -1  # 説明用に「-1 = 無限」としています。

    @property
    def total_value(self) -> int:
        """在庫総額は意味がないので、0を返します。"""
        return 0

    def add_stock(self, amount: int):
        """デジタル商品の在庫は増やす必要がないので、何もしません。"""
        print("デジタル商品の在庫は管理していません。")

    def remove_stock(self, amount: int):
        """デジタル商品の在庫は減らす必要がないので、何もしません。"""
        print("デジタル商品の在庫は管理していません。")

    def show_info(self):
        """商品情報を表示するメソッド(オーバーライド)。"""
        print("=== デジタル商品 ===")
        print(f"商品名: {self.name}")
        print(f"価格(税抜): {self.price} 円")
        print("在庫: 無制限")


class ProductManager:
    """商品をまとめて管理するクラスです。"""

    def __init__(self):
        self._products = []

    def add_product(self, product: Product):
        """商品を追加します。"""
        self._products.append(product)

    def list_products(self):
        """登録されている商品を一覧表示します。"""
        print("=== 商品一覧 ===")
        if not self._products:
            print("商品が登録されていません。")
            return

        for idx, product in enumerate(self._products, start=1):
            print(f"\n[{idx}]")
            product.show_info()

    def find_by_name(self, name: str) -> Product | None:
        """商品名で検索し、見つかった商品を返します。見つからなければNoneを返します。"""
        for product in self._products:
            if product.name == name:
                return product
        return None


def main():
    print("=== Day 24:商品管理システム ===")

    manager = ProductManager()

    # 物理商品を登録します。
    laptop = PhysicalProduct("ノートPC", price=120000, stock=5, shipping_fee=1000, tax_rate=0.1)
    book = PhysicalProduct("技術書", price=3000, stock=10, shipping_fee=500, tax_rate=0.1)

    # デジタル商品を登録します。
    ebook = DigitalProduct("電子書籍(PDF版)", price=2000)
    software = DigitalProduct("ソフトウェアライセンス", price=15000)

    manager.add_product(laptop)
    manager.add_product(book)
    manager.add_product(ebook)
    manager.add_product(software)

    # 商品一覧を表示します。
    manager.list_products()

    # 在庫操作の例
    print("\n=== 在庫操作の例 ===")
    target = manager.find_by_name("ノートPC")
    if target is not None:
        print("ノートPCの在庫を3個追加します。")
        target.add_stock(3)
        target.show_info()

    target = manager.find_by_name("電子書籍(PDF版)")
    if target is not None:
        print("電子書籍の在庫を減らそうとします(意味がない例)。")
        target.remove_stock(1)
        target.show_info()


if __name__ == "__main__":
    main()
Python

Day 24(制作編)のまとめ

Day 24の制作編では、「商品管理システム」を通して、

  • 継承
    • Product を親クラスとして、PhysicalProductDigitalProduct を派生クラスとして設計する流れ
  • オーバーライド
    • show_info や在庫関連メソッドを子クラスで書き換え、 「同じメソッド名で、商品種別ごとに振る舞いを変える」感覚
  • @property
    • pricestocktotal_valueprice_with_tax などをプロパティとして扱い、 「計算が必要な値を属性のように自然に扱う」書き方
  • カプセル化
    • _price_stock を内部属性とし、 setterやメソッドの中に「不正な操作を防ぐルール」を閉じ込める設計

を、ひとつのまとまったプログラムの中で体験していただきました。

ここまで来ると、「オブジェクト指向の機能を、現実のドメイン(商品管理)の中でどう使うか」という感覚が少し育っているはずです。 この感覚は、今後より大きなシステムや複雑なビジネスロジックを扱うときに、コードを整理し続けるための強力な土台になっていきます。

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