オブジェクト指向プログラミングにおいて、クラスの責務を明確にし、モジュール性を高めることは非常に重要です。
本記事では、クラスが自分自身の責務に集中することで、コードのモジュール性を維持し、変更に強い設計を実現する方法について解説します。
クラスが他のクラスの詳細に関心を持ちすぎると問題が起きる
以下のコードは、ショッピングカートとアイテムを表す2つのクラスの例です。
class Item: def __init__(self, name, price): self.name = name self.price = price class ShoppingCart: def __init__(self): self.items = [] def add_item(self, item): self.items.append(item) def remove_item(self, item): self.items.remove(item) def calculate_total_price(self): return sum(item.price for item in self.items) def apply_discount(self, discount_percentage): for item in self.items: item.price *= (1 - discount_percentage / 100)
この例では、ShoppingCartクラスがItemクラスの詳細(priceプロパティ)に直接アクセスしています。
つまり、ShoppingCartクラスはItemクラスの内部実装に依存しています。
この設計には以下のような問題があります。
- Itemクラスの実装が変更された場合(例えば、priceプロパティがprice_in_centsに変更された場合)、ShoppingCartクラスも変更しなければなりません。
- 割引を適用する際、ShoppingCartクラスがItemクラスのpriceプロパティを直接変更しているため、カプセル化が破られています。
このように、あるクラスが他のクラスの詳細に関心を持ちすぎると、コードのモジュール性が低下し、変更への対応が難しくなります。
解決策: クラスが自分自身の責務に集中し、依存性の注入を活用する
コードのモジュール性を維持するためには、クラスができる限り自分自身の責務に集中することです。
そして、他のクラスの詳細については最小限の知識しか持たないようにする必要があります。
また、依存性の注入(DI)を活用することで、クラス間の結合度を下げることができます。
DIについては、以下の記事で説明しています。
以下のコードは、ShoppingCartクラスとItemクラスを改善し、DIを適用した例です。
class Item: def __init__(self, name, price): self.name = name self.price = price def get_price(self): return self.price def apply_discount(self, discount_percentage): self.price *= (1 - discount_percentage / 100) class ShoppingCart: def __init__(self, items=None): self.items = items or [] def add_item(self, item): self.items.append(item) def remove_item(self, item): self.items.remove(item) def calculate_total_price(self): return sum(item.get_price() for item in self.items) def apply_discount(self, discount_percentage): for item in self.items: item.apply_discount(discount_percentage)
この改善された設計では、以下の変更が加えられています。
- Itemクラスにget_priceメソッドを追加し、ShoppingCartクラスではこのメソッドを介して価格を取得するようにしました。これにより、ShoppingCartクラスはItemクラスの内部実装に直接依存しなくなります。
- 割引の適用をItemクラスの責務とし、apply_discountメソッドを追加しました。
- ShoppingCartクラスのコンストラクタがitems引数を受け取るようになり、DIの原則を適用しました。これにより、ShoppingCartインスタンスを作成する際に、必要なItemインスタンスのリストを外部から注入できます。
この設計では、ShoppingCartクラスとItemクラスがそれぞれ自分自身の責務に集中しています。
また、DIを活用することで、クラス間の結合度を下げています。
以下は、この設計を使用する例です。
item1 = Item("Apple", 1.0) item2 = Item("Banana", 1.5) item3 = Item("Orange", 2.0) items = [item1, item2, item3] cart = ShoppingCart(items) print(cart.calculate_total_price()) # 4.5 cart.apply_discount(10) print(cart.calculate_total_price()) # 4.05
この例では、Itemインスタンスを作成し、それらをShoppingCartコンストラクタに渡しています。
これにより、ShoppingCartインスタンスに必要な依存関係が注入されます。
まとめ
クラスができる限り自分自身の責務に集中し、他のクラスの詳細に関する知識を最小限に抑えることは、コードのモジュール性を維持するために重要です。
また、依存性の注入(DI)を活用することで、クラス間の結合度を下げ、コードの保守性を高めることができます。
設計の際には、以下の点に留意しましょう。
- 各クラスが自分自身の責務に集中しているか
- クラス間の結合度が適切か
- カプセル化が守られているか
- 依存性の注入を活用できるか
これらの原則に従うことで、より堅牢で保守性の高いオブジェクト指向設計を実現できるでしょう。