Pythonで学ぶSOLID原則

Pythonを使ってSOLID原則を説明したいと思います。
コード例は本当によくある例になります。

### 1. **単一責任の原則（Single Responsibility Principle: SRP）**

クラスは一つの責任だけを持つべきです。例えば、ファイルの読み込みとログの書き込みを同じクラスで行うのはNGです。

**良くない例:**

```python
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')
```

**良い例:**

```python
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）**

クラスは拡張には開かれており、修正には閉じているべきです。既存のクラスを変更せずに新しい機能を追加できる設計が理想です。

**良くない例:**

```python
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}")
```

この設計では、新しい支払い方法を追加するたびにクラスを修正する必要があります。

**良い例:**

```python
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）**

サブクラスは、基底クラスの代わりに使用できるべきです。つまり、サブクラスが基底クラスの契約を壊してはなりません。

**良くない例:**

```python
class Bird:
    def fly(self):
        print("Flying")

class Ostrich(Bird):
    def fly(self):
        raise Exception("Ostriches can't fly")
```

この場合、`Ostrich`クラスは`Bird`クラスの置き換えにはなりません。

**良い例:**

```python
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では多重継承やプロトコルを使うことでこれを実現できます。

**良くない例:**

```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")
```

**良い例:**

```python
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）**

高レベルのモジュールは低レベルのモジュールに依存すべきではなく、両者とも抽象化に依存すべきです。

**良くない例:**

```python
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()
```

**良い例:**

```python
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`デバイスを操作できるようになり、柔軟性が向上します。