目次
season2
~あらすじ~
クラスの概念を身につけた(クラスを作ることしか知らない)プログラマーのAさん。チーム開発でもクラスを用いてブイブイ言わせるが….?
class Employee:
def __init__(self, name, rating, rank_multiplier, base_salary):
self.name = name
self.rating = rating
self.rank_multiplier = rank_multiplier
self.salary = self.calculate_salary()
self.base_salary = base_salary
# 給料を計算するメソッド
def calculate_salary(self):
return self.rating * self.rank_multiplier * self.base_salary
# 給料を更新するメソッド
def update_salary(self):
self.salary = self.calculate_salary()
# 従業員オブジェクトの作成
employee = Employee(name="田中太郎", rating=85, rank_multiplier=1.2, base_salary=50000)
print(f"Name: {employee.name}")
print(f"Salary: {employee.salary}")
ある日、偉い人からシステムの変更を依頼されました。
employee = Employee(name="田中太郎", rating=85, rank_multiplier=1.2, base_salary=50000)
employee.salary += 1000
~後日~
ドメイン知識もなかったBさんは本来(評価点、階級倍率、ベース給料)の3つから計算される給料にあやまって変更を加えてしまい、システム全体のデータ整合性が損なわれてしまいました。
いったいどうすればこのような問題を防げたのでしょうか?
カプセル化を用いない問題点
- データに自由にアクセスできたため、データに不整合が生じた。
- データに自由にアクセスできたため、コードが複雑になる可能性があった。
-
データにバリデーションがないため
base_salary=-50000
とすると給料がマイナスになる可能性があった。
カプセル化
- オブジェクト内部のデータや振る舞いを隠蔽してオブジェクト外部からの操作を制御することで、オブジェクトの独立性を保つための仕組み
1. データの保護
カプセル化により、オブジェクト内部のデータは直接アクセスや変更から保護されます。これにより、データの不整合や誤った操作を防ぐことができます。
2. 複雑さの管理
オブジェクト内部の実装の詳細を隠すことで、複雑さを管理しやすくなります。外部からはインターフェース(メソッドやプロパティ)を通じてのみアクセスできるため、内部の変更が外部に影響を与えにくくなります。
3. メンテナンスの容易化
内部実装を変更する場合でも、外部インターフェースさえ変更しなければ、既存のコードに影響を与えずに済みます。これにより、システムのメンテナンスが容易になります。
4. モジュール化の推進
カプセル化は、ソフトウェアのモジュール化を推進します。オブジェクトごとに独立した機能を持たせることで、各オブジェクトが自己完結型のモジュールとなり、再利用性が向上します。
5. デバッグの容易化
データの隠蔽により、バグが発生した場合に原因を特定しやすくなります。データのアクセスが制限されることで、問題のあるコードがどこにあるかを絞り込みやすくなります。
6. 安全性の向上
データに対するアクセス権を制御することで、誤った使用や不正なアクセスからシステムを保護できます。特にセキュリティが重要なシステムでは、カプセル化は大きなメリットとなります。
7. 一貫性の維持
オブジェクトが提供するインターフェースを通じてのみデータを操作するため、データの一貫性を保つことができます。これにより、予期しない動作やバグの発生を防ぐことができます。
カプセル化の方法
インスタンス変数に対してネームマングリングを行った場合
-
直接インスタンス変数を参照することはできず、メソッドを介して参照できる
class Order: def __init__(self, order_id, item_name, price, quantity): self.__order_id = order_id self.__item_name = item_name self.__price = price self.__quantity = quantity def get_total_price(self): return self.__price * self.__quantity def get_order_info(self): return (f'注文番号: {self.__order_id}, 商品名: {self.__item_name}, ' f'価格: {self.__price}, 数量: {self.__quantity}, 合計金額: {self.get_total_price()}') order1 = Order(1, "apple", 120, 4, ) print(order1.price)
Traceback (most recent call last): File "/home/jail/prog.py", line 18, in <module> print(order1.price) ^^^^^^^^^^^^ AttributeError: 'Order' object has no attribute 'price'
メソッドに対してネームマングリングを行った場合
-
メソッドを呼び出すことはできない(クラス内のメソッドからは呼び出せる)
class Order: def __init__(self, order_id, item_name, price, quantity): self.__order_id = order_id self.__item_name = item_name self.__price = price self.__quantity = quantity def get_total_price(self): return self.__price * self.__quantity def __get_order_info(self): return (f'注文番号: {self.__order_id}, 商品名: {self.__item_name}, ' f'価格: {self.__price}, 数量: {self.__quantity}, 合計金額: {self.get_total_price()}') order1 = Order(1, "apple", 120, 4, ) print(order1.__get_order_info())
Traceback (most recent call last): File "/home/jail/prog.py", line 18, in <module> print(order1.__get_order_info()) ^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: 'Order' object has no attribute '__get_order_info'. Did you mean: '_Order__get_order_info'?
社員管理システムをカプセル化する
class Employee: def __init__(self, name, rating, rank_multiplier, base_salary): self.name = name self.__rating = rating self.__rank_multiplier = rank_multiplier self.__base_salary = base_salary self.__salary = self.__calculate_salary() # 評価点のゲッターとセッター @property def rating(self): return self.__rating @rating.setter def rating(self, value): if 0 <= value <= 100: self.__rating = value self.__update_salary() else: raise ValueError("Rating must be between 0 and 100") # 階級倍率のゲッターとセッター @property def rank_multiplier(self): return self.__rank_multiplier @rank_multiplier.setter def rank_multiplier(self, value): if value > 0: self.__rank_multiplier = value self.__update_salary() else: raise ValueError("Rank multiplier must be positive") # 基本給のゲッターとセッター @property def base_salary(self): return self.__base_salary @base_salary.setter def base_salary(self, value): if value >= 0: self.__base_salary = value self.__update_salary() else: raise ValueError("Base salary must be non-negative") # 給料のゲッター @property def salary(self): return self.__salary # 給料を計算するメソッド def __calculate_salary(self): return self.__rating * self.__rank_multiplier * self.__base_salary # 給料を更新するメソッド def __update_salary(self): self.__salary = self.__calculate_salary() # 従業員オブジェクトの作成 employee = Employee(name="田中太郎", rating=85, rank_multiplier=1.2, base_salary=50000) print(f"Name: {employee.name}") print(f"Salary: {employee.salary}") # 不正な操作を試みる try: employee.salary = 600000000 # 直接変更できない(無視される) except AttributeError as e: print(e) try: employee.rating = 110 # 不正な評価点(例外が発生) except ValueError as e: print(e) try: employee.rank_multiplier = -1.3 # 不正な階級倍率(例外が発生) except ValueError as e: print(e) # 有効な値の設定 employee.rating = 90 employee.rank_multiplier = 1.5 employee.base_salary = 55000 print(f"Updated Salary: {employee.salary}") # 更新された給料を表示
-
プライベート属性
:
__rating
,__rank_multiplier
,__base_salary
,__salary
などの属性はプライベート(先頭に__
を付ける)として定義されています。これにより、これらの属性は外部から直接アクセスできなくなりました。 -
プロパティ
:
rating
,rank_multiplier
,base_salary
,salary
などの属性に対してプロパティを使用し、ゲッターとセッターを定義しています。これにより、属性にアクセスする際に検証が行われ、不正な値の設定を防止しました。 -
例外処理
: 不正な値が設定されると、
ValueError
を発生させます。これにより、データの整合性が保たれました。 -
給料の自動計算
:
rating
,rank_multiplier
,base_salary
のいずれかが変更されると、__update_salary
メソッドが呼び出され、給料が自動的に再計算されました。
この実装により、従業員管理クラスはカプセル化され、安全で一貫性のあるデータ管理が可能になりました。
-
プライベート属性
:
-
メソッドを呼び出すことはできない(クラス内のメソッドからは呼び出せる)