Pythonのオブジェクト指向プログラミング
OOPは、状態を持ち明確なインターフェースで振る舞いを提供するオブジェクトを中心にプログラムを構成します。
Pythonのオブジェクト指向プログラミングとは?
OOPは協調するオブジェクトとしてシステムをモデル化します。各オブジェクトが状態と許可された操作をまとめるため、大規模なプログラムでも責務が明確になります。
OOPの4つの基本原則
- カプセル化:状態と振る舞いをまとめ、アクセスを制御します。
- 抽象化:必要なインターフェースを示し、不要な実装詳細を隠します。
- 継承:真のis-a関係がある場合に既存クラスを特殊化します。
- ポリモーフィズム:異なるオブジェクトに同じ操作を適用し、それぞれの振る舞いを実行します。
この例の設計
- BankAccountが残高を所有し入金を検証します。
- SavingsAccountが基底クラスの契約を再利用します。
- month_endをオーバーライドして利息を適用します。
- ループはすべての口座を同じインターフェースで扱います。
設計のヒント: 単に別のオブジェクトを利用するだけなら合成を優先し、子が親を正しく置換できる場合だけ継承を使います。
基本例
main.py
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = float(balance)
@property
def balance(self):
return self._balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self._balance += amount
def month_end(self):
return "No interest"
class SavingsAccount(BankAccount):
def __init__(self, owner, balance, interest_rate):
super().__init__(owner, balance)
self.interest_rate = interest_rate
def month_end(self):
self._balance *= 1 + self.interest_rate
return "Interest applied"
accounts = [
BankAccount("Ada", 100),
SavingsAccount("Lin", 200, 0.05),
]
accounts[0].deposit(50)
for account in accounts:
status = account.month_end()
print(f"{account.owner}: {account.balance:.2f} ({status})")
期待される出力
Ada: 150.00 (No interest)
Lin: 210.00 (Interest applied)
OOPの4原則を実践する
これらの例では、Pythonでカプセル化、抽象化、継承、ポリモーフィズムを日常的なコードに適用する方法を示します。
カプセル化
状態と振る舞いをまとめ、アクセスを制御します。
encapsulation.py
class BankAccount:
def __init__(self, balance=0):
# Double underscores prevent accidental direct access.
self.__balance = balance
@property
def balance(self):
# Expose read access without exposing mutation.
return self.__balance
def deposit(self, amount):
# Keep validation next to the state it protects.
if amount <= 0:
raise ValueError("Amount must be positive")
self.__balance += amount
account = BankAccount(100)
account.deposit(50)
print(account.balance) # 150
抽象化
必要なインターフェースを示し、不要な実装詳細を隠します。
abstraction.py
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def pay(self, amount):
# Callers know what pay does, not how each provider does it.
pass
class CreditCard(PaymentMethod):
def pay(self, amount):
# The concrete class hides its payment implementation.
return f"Paid ${amount:.2f} by card"
payment = CreditCard()
print(payment.pay(25))
継承
真のis-a関係がある場合に既存クラスを特殊化します。
inheritance.py
class Employee:
def __init__(self, name):
self.name = name
def describe(self):
return self.name
class Developer(Employee):
def __init__(self, name, language):
# Reuse initialization from the parent class.
super().__init__(name)
self.language = language
def describe(self):
# Extend inherited behavior instead of duplicating it.
return f"{super().describe()} writes {self.language}"
print(Developer("Ada", "Python").describe())
ポリモーフィズム
異なるオブジェクトに同じ操作を適用し、それぞれの振る舞いを実行します。
polymorphism.py
class EmailNotification:
def send(self, message):
return f"Email: {message}"
class SmsNotification:
def send(self, message):
return f"SMS: {message}"
def notify(channels, message):
for channel in channels:
# The same call selects behavior from each concrete object.
print(channel.send(message))
channels = [EmailNotification(), SmsNotification()]
notify(channels, "Order shipped")
仕組み
BankAccountは残高をカプセル化します。SavingsAccountはそのインターフェースを継承してmonth_endをオーバーライドします。ループでは同じ方法で扱い、実行時に適切なメソッドが選択されます。
値を変更し、PythonをインストールせずにCodeUtilityオンラインPythonコンパイラで実行できます。
練習問題
入力値と境界ケースを変更し、より大きなデータでも動作を確認してください。
- 実行前に出力を予測する。
- 空または不正な入力を処理する。
- 処理を関数化し、テストケースを追加する。