Pythonを使ってSOLID原則を説明したいと思います。
コード例は本当によくある例になります。
1. 単一責任の原則(Single Responsibility Principle: SRP)
クラスは一つの責任だけを持つべきです。例えば、ファイルの読み込みとログの書き込みを同じクラスで行うのはNGです。
良くない例:
class FileManager:
def read_file(self, filename):
with open(filename, 'r') as file:
return file.read()
def write_log(self, message):
with open('log.txt', 'a') as log_file:
log_file.write(message + '\n')
良い例:
class FileReader:
def read_file(self, filename):
with open(filename, 'r') as file:
return file.read()
class Logger:
def write_log(self, message):
with open('log.txt', 'a') as log_file:
log_file.write(message + '\n')
2. オープン・クローズドの原則(Open/Closed Principle: OCP)
クラスは拡張には開かれており、修正には閉じているべきです。既存のクラスを変更せずに新しい機能を追加できる設計が理想です。
良くない例:
class PaymentProcessor:
def process(self, payment_method, amount):
if payment_method == 'credit_card':
self.process_credit_card(amount)
elif payment_method == 'paypal':
self.process_paypal(amount)
def process_credit_card(self, amount):
print(f"Processing credit card payment of {amount}")
def process_paypal(self, amount):
print(f"Processing PayPal payment of {amount}")
この設計では、新しい支払い方法を追加するたびにクラスを修正する必要があります。
良い例:
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def process(self, amount):
pass
class CreditCardProcessor(PaymentProcessor):
def process(self, amount):
print(f"Processing credit card payment of {amount}")
class PayPalProcessor(PaymentProcessor):
def process(self, amount):
print(f"Processing PayPal payment of {amount}")
def process_payment(processor: PaymentProcessor, amount):
processor.process(amount)
この設計では、新しい支払い方法を追加するときに、新しいクラスを作成するだけで済み、既存のコードを修正する必要がありません。
3. リスコフの置換原則(Liskov Substitution Principle: LSP)
サブクラスは、基底クラスの代わりに使用できるべきです。つまり、サブクラスが基底クラスの契約を壊してはなりません。
良くない例:
class Bird:
def fly(self):
print("Flying")
class Ostrich(Bird):
def fly(self):
raise Exception("Ostriches can't fly")
この場合、Ostrich
クラスはBird
クラスの置き換えにはなりません。
良い例:
from abc import ABC, abstractmethod
class Bird(ABC):
@abstractmethod
def move(self):
pass
class Sparrow(Bird):
def move(self):
print("Flying")
class Ostrich(Bird):
def move(self):
print("Running")
ここでは、move
メソッドが鳥の移動手段を表し、どのサブクラスも正しく振る舞います。
4. インターフェース分離の原則(Interface Segregation Principle: ISP)
クライアントが使わないメソッドへの依存を強制されるべきではありません。Pythonでは多重継承やプロトコルを使うことでこれを実現できます。
良くない例:
class Worker:
def work(self):
pass
def eat(self):
pass
class HumanWorker(Worker):
def work(self):
print("Working")
def eat(self):
print("Eating")
class RobotWorker(Worker):
def work(self):
print("Working")
def eat(self):
raise NotImplementedError("Robots don't eat")
良い例:
class Workable(ABC):
@abstractmethod
def work(self):
pass
class Eatable(ABC):
@abstractmethod
def eat(self):
pass
class HumanWorker(Workable, Eatable):
def work(self):
print("Working")
def eat(self):
print("Eating")
class RobotWorker(Workable):
def work(self):
print("Working")
5. 依存性逆転の原則(Dependency Inversion Principle: DIP)
高レベルのモジュールは低レベルのモジュールに依存すべきではなく、両者とも抽象化に依存すべきです。
良くない例:
class LightBulb:
def turn_on(self):
print("LightBulb: On")
def turn_off(self):
print("LightBulb: Off")
class Switch:
def __init__(self, bulb: LightBulb):
self.bulb = bulb
def operate(self, state: str):
if state == "on":
self.bulb.turn_on()
else:
self.bulb.turn_off()
良い例:
from abc import ABC, abstractmethod
class Switchable(ABC):
@abstractmethod
def turn_on(self):
pass
@abstractmethod
def turn_off(self):
pass
class LightBulb(Switchable):
def turn_on(self):
print("LightBulb: On")
def turn_off(self):
print("LightBulb: Off")
class Switch:
def __init__(self, device: Switchable):
self.device = device
def operate(self, state: str):
if state == "on":
self.device.turn_on()
else:
self.device.turn_off()
これにより、Switch
は任意のSwitchable
デバイスを操作できるようになり、柔軟性が向上します。