🚀 ニフティ’s Notion

【オブジェクト指向2024 #2】クラスとインスタンス

目次

season1

~クラスの概念が無い世界線~
偉い人: 注文管理のプログラムを作ってくれないか?
A: わかりました!すぐやります!

オブジェクト指向プログラミング(OOP)のクラスの概念を知らないプログラマーのAさんは、複雑な業務フローを持つ注文管理を作ろうとしているようです。

ところが、数々の問題が発生するようです….

A: カタカタカタ ….タンッ!!!
# 注文リスト
orders = []

# 商品の在庫
inventory = {
    "apple": 10,
    "banana": 15,
    "orange": 12
}

# 注文作成
def create_order(order_id, item, quantity, order_date):
    if item in inventory and inventory[item] >= quantity:
        orders.append({"order_id": order_id, "item": item, "quantity": quantity, "order_date": order_date})
        inventory[item] -= quantity
        print(f"注文が作成されました: ID {order_id}, {quantity}個の{item} を注文しました ({order_date})")
        print(f"在庫が更新されました: {item} の在庫は {inventory[item]} 個です")
    else:
        print("注文を作成できませんでした。在庫が不足しているか、商品が存在しません。")

# 注文読み込み(全ての注文を表示)
def read_orders():
    print("\n注文リスト:")
    for order in orders:
        print(order)

# 注文更新
def update_order(order_id, new_quantity):
    (省略)

# 注文削除
def delete_order(order_id):
    (省略)

# 注文を作成
create_order(1, "apple", 3, "2024-04-22")
create_order(2, "banana", 5, "2024-04-22")
create_order(3, "grape", 2, "2024-04-22")

# 全ての注文を表示
read_orders()

# 注文を更新
update_order(2, 7)

# 注文を削除
delete_order(3)

# 更新された注文リストを表示
read_orders()
完全版
# 注文リスト
orders = []

# 商品の在庫
inventory = {
    "apple": 10,
    "banana": 15,
    "orange": 12
}

# 注文作成
def create_order(order_id, item, quantity, order_date):
    if item in inventory and inventory[item] >= quantity:
        orders.append({"order_id": order_id, "item": item, "quantity": quantity, "order_date": order_date})
        inventory[item] -= quantity
        print(f"注文が作成されました: ID {order_id}, {quantity}個の{item} を注文しました ({order_date})")
        print(f"在庫が更新されました: {item} の在庫は {inventory[item]} 個です")
    else:
        print("注文を作成できませんでした。在庫が不足しているか、商品が存在しません。")

# 注文読み込み(全ての注文を表示)
def read_orders():
    print("\n注文リスト:")
    for order in orders:
        print(order)

# 注文更新
def update_order(order_id, new_quantity):
    for order in orders:
        if order["order_id"] == order_id:
            item = order["item"]
            old_quantity = order["quantity"]
            if item in inventory and inventory[item] + old_quantity >= new_quantity:
                inventory[item] += old_quantity - new_quantity
                order["quantity"] = new_quantity
                print(f"注文が更新されました: ID {order_id}, {item} の数量が {old_quantity} から {new_quantity} に変更されました")
                print(f"在庫が更新されました: {item} の在庫は {inventory[item]} 個です")
            else:
                print("注文を更新できませんでした。在庫が不足しているか、商品が存在しません。")
            return
    print("指定されたIDの注文が見つかりませんでした。")

# 注文削除
def delete_order(order_id):
    for order in orders:
        if order["order_id"] == order_id:
            item = order["item"]
            quantity = order["quantity"]
            inventory[item] += quantity
            orders.remove(order)
            print(f"注文が削除されました: ID {order_id}, {quantity}個の{item} の注文がキャンセルされました")
            print(f"在庫が更新されました: {item} の在庫は {inventory[item]} 個です")
            return
    print("指定されたIDの注文が見つかりませんでした。")

# 注文を作成
create_order(1, "apple", 3, "2024-04-22")
create_order(2, "banana", 5, "2024-04-22")
create_order(3, "grape", 2, "2024-04-22")

# 全ての注文を表示
read_orders()

# 注文を更新
update_order(2, 7)

# 注文を削除
delete_order(3)

# 更新された注文リストを表示
read_orders()

偉い人: ありがとう。ところで顧客情報も注文管理で分かるようにしたいんだけど、できる?
A: わかりました!すぐやります!

~~~~~このようなやり取りがn回繰り返される~~~~~

偉い人: なんども悪いんだけど、〇〇の機能を追加してくれる?
A: わかりました!すぐやります!(だんだん機能追加に時間がかかってる気がするな..)
# 注文リスト
orders = []

# 商品の在庫
inventory = {
    "apple": 10,
    "banana": 15,
    "orange": 12
}

# 注文作成
def create_order(order_id, item, quantity, order_date):
    (省略)

# 商品の在庫
inventory2 = {
    "car": 10,
    "bike": 15,
    "ship": 12
}

~~~~数千行~~~~

# 注文読み込み(全ての注文を表示)
def read_orders():
    (省略)

# 注文更新
def update_order(order_id, new_quantity):
    (省略)

# 注文削除
def delete_order(order_id):
    (省略)


~~~~数千行~~~~

# 注文を作成
create_order(1, "apple", 3, "2024-04-22")
create_order(2, "banana", 5, "2024-04-22")
create_order(3, "grape", 2, "2024-04-22")

# 全ての注文を表示
read_orders()

# 注文を更新
update_order(2, 7)

# 注文を削除
delete_order(3)

# 更新された注文リストを表示
read_orders()

A: コードがデカくなりすぎて どの関数がどのデータに依存しているのか分からない
  コードを 変更するのにヤバいくらい時間がかかる ….(コードリーディングしてる時間が多い)
   バグが起こりやすく なった…
A: ヴゥ…….

クラスの概念を知らなかった故に、Aさんは忙殺され廃人になってしまいました。

なぜこうなったか?
  1. 構造の欠如 : 関連するデータと機能をまとめる手段がありません。関数と変数が単純にグローバルスコープに存在し、それらがどのように関連しているかを把握するのは非常に困難です。
  2. 保守性の低下 : 関数と変数が散在しているため、コードの変更や修正が困難になります。特定の機能やデータを変更する際に、影響を受ける部分を特定することが難しくなっています。
  3. 再利用の困難さ : 類似の機能を持つコードを再利用することが難しくなります。関数や変数が単一のコンテキストに依存しているため、他のプログラムで同じ機能を再利用することが難しくなっています。
  4. データの整合性の欠如 : 関数がグローバルな変数に直接アクセスするため、データの整合性を保つことが難しくなっています。複数の関数が同じ変数を操作する場合、競合状態や不整合が発生する可能性があります。
  5. 抽象化の不足 : 機能やデータを適切に抽象化する手段が限られており、コードの理解とメンテナンスが難しくなっています。

オブジェクト指向で出てくる用語

クラス
  • クラスとはオブジェクトの設計図にあたるもの。
  • クラスにはデータと操作(メソッド)が定義される
      • Order(注文クラス)が定義されている。
        • データとしてorder_id(注文番号)やprice(料金)を持つ。
        • get_order_infoというメソッドを定義し、注文の詳細を画面に出力するようにしている
      class Order:
          def __init__(self, order_id, item_name, price, quantity):  # インスタンスを生成する際に呼び出される特殊メソッド (コンストラクタ)
      		    """
              :param order_id: 注文番号
              :param item_name: 商品名
              :param price: 価格
              :param quantity: 数量
              """
              self.order_id = order_id      
              self.item_name = item_name 
              self.price = price                  
              self.quantity = quantity     
      
          def get_total_price(self):  # Orderクラスのインスタンスメソッド
              return self.price * self.quantity
      
          def get_order_info(self):  # Orderクラスのインスタンスメソッド
              return (f'注文番号: {self.order_id}, 商品名: {self.item_name}, '
                      f'価格: {self.price}, 数量: {self.quantity}, 合計金額: {self.get_total_price()}')

      実際に動かしてみよう

インスタンス
  • クラス定義に基づいてメモリ上に領域が確保され、クラスを実体化したもの。
image block
  • 生成されたデータの集合体を「インスタンス」と呼ぶ。
属性:データ
  • インスタンスは、固有の姿・形・性質などを持っており、このようなインスタンスの状態を属性(attribute)として表現する。
    • 注文クラスにおいて、注文番号、商品名、単価、注文数が属性に当たる
メソッド
  • オブジェクトが持っている処理・ふるまいのこと。
    • そのオブジェクトに関する操作
  • 注文クラスにおいて、合計の値段を計算する処理、注文の詳細を表示する操作がメソッドに当たる

image block
クラスとインスタンスとオブジェクトの違い
  • クラス
    • データとメソッドを定義したもの
    • インスタンスの設計図
    • インスタンスが持つことができる性質を規定
    • コード上でクラスを定義する(=静的)
      • あくまでも設計図のため、実行時にクラスそのものをいじることはない
    • 可能なデータ構造の集合を示している
  • インスタンス
    • クラスを実体化したもの
    • 実行時に、クラスを元にメモリに展開され使用される(=動的)
    • クラスとインスタンスは1対多の関係
      • 異なる属性値を持つ実体を複数作ることができる
      • クラスによって定義された可能なデータ構造の集合の要素の一つを示している
  • オブジェクト
    • 実体の総称
    • クラスだけでなく、整数、実数にも適用
    • オブジェクト指向では、インスタンスと同じ意味で使われることもあるが、オブジェクトはインスタンスをより抽象的にした用語

クラスを作る上で大事なポイント

  • クラス単体で正常に動作するように設計する
    • クラスが自分自身を守る
  • 自分自身を守るためには?
    • インスタンス変数を守る、つまりデータが不正な状態に陥らないようにする
  • インスタンス変数を守るためには?
    • クラス内に入力値をバリデーションする仕組みを導入し、インスタンス変数に不正なデータが混入しないようにする
    • メソッド内部でインスタンス変数を変更する場合も、不正なデータを扱わないようにする
  • クラスは単なるデータの入れ物ではない
    • メソッドを使って内部状態を防御する

序章で出てきたものにクラスを用いる

class Order:
    def __init__(self):
        self.orders = []
        self.inventory = {
            "apple": 10,
            "banana": 15,
            "orange": 12
        }

    # 注文作成
    def create_order(self, order_id, item, quantity, order_date):
        if item in self.inventory and self.inventory[item] >= quantity:
            self.orders.append({"order_id": order_id, "item": item, "quantity": quantity, "order_date": order_date})
            self.inventory[item] -= quantity
            print(f"注文が作成されました: ID {order_id}, {quantity}個の{item} を注文しました ({order_date})")
            print(f"在庫が更新されました: {item} の在庫は {self.inventory[item]} 個です")
        else:
            print("注文を作成できませんでした。在庫が不足しているか、商品が存在しません。")

    # 注文読み込み(全ての注文を表示)
    def read_orders(self):
        print("\n注文リスト:")
        for order in self.orders:
            print(order)

    # 注文更新
    def update_order(self, order_id, new_quantity):
        for order in self.orders:
            if order["order_id"] == order_id:
                item = order["item"]
                old_quantity = order["quantity"]
                if item in self.inventory and self.inventory[item] + old_quantity >= new_quantity:
                    self.inventory[item] += old_quantity - new_quantity
                    order["quantity"] = new_quantity
                    print(f"注文が更新されました: ID {order_id}, {item} の数量が {old_quantity} から {new_quantity} に変更されました")
                    print(f"在庫が更新されました: {item} の在庫は {self.inventory[item]} 個です")
                else:
                    print("注文を更新できませんでした。在庫が不足しているか、商品が存在しません。")
                return
        print("指定されたIDの注文が見つかりませんでした。")

    # 注文削除
    def delete_order(self, order_id):
        for order in self.orders:
            if order["order_id"] == order_id:
                item = order["item"]
                quantity = order["quantity"]
                self.inventory[item] += quantity
                self.orders.remove(order)
                print(f"注文が削除されました: ID {order_id}, {quantity}個の{item} の注文がキャンセルされました")
                print(f"在庫が更新されました: {item} の在庫は {self.inventory[item]} 個です")
                return
        print("指定されたIDの注文が見つかりませんでした。")

# Orderrのインスタンスを作成
order = Order()

# 注文を作成
order.create_order(1, "apple", 3, "2024-04-22")
order.create_order(2, "banana", 5, "2024-04-22")
order.create_order(3, "grape", 2, "2024-04-22")

# 全ての注文を表示
order.read_orders()

# 注文を更新
order.update_order(2, 7)

# 注文を削除
order.delete_order(3)

# 更新された注文リストを表示
order.read_orders()

このように、クラスを使用すると関連するデータとメソッドが一つのまとまりになり、コードがより構造化されて保守しやすくなりました。

また、オブジェクト指向の特性を活用して、関連する操作を容易に実装できます。


次へ: 📄 【オブジェクト指向2024 #3】カプセル化