SOLID原則まとめ (C#サンプルコード付き)

## はじめに

SOLID原則とは、オブジェクト指向設計において、保守性や拡張性を高めるための5つの基本原則になります。

1. [単一責任の原則（Single Responsibility Principle: SRP）](https://qiita.com/i-tanaka730/items/993b4f6c1a701657788e#1-%E5%8D%98%E4%B8%80%E8%B2%AC%E4%BB%BB%E3%81%AE%E5%8E%9F%E5%89%87srp)
2. [開放/閉鎖の原則（Open/Closed Principle: OCP）](https://qiita.com/i-tanaka730/items/993b4f6c1a701657788e#2-%E9%96%8B%E6%94%BE%E9%96%89%E9%8E%96%E3%81%AE%E5%8E%9F%E5%89%87ocp)
3. [リスコフの置換原則（Liskov Substitution Principle: LSP）](https://qiita.com/i-tanaka730/items/993b4f6c1a701657788e#3-%E3%83%AA%E3%82%B9%E3%82%B3%E3%83%95%E3%81%AE%E7%BD%AE%E6%8F%9B%E5%8E%9F%E5%89%87lsp)
4. [インターフェース分離の原則（Interface Segregation Principle: ISP）](https://qiita.com/i-tanaka730/items/993b4f6c1a701657788e#4-%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E5%88%86%E9%9B%A2%E3%81%AE%E5%8E%9F%E5%89%87isp)
5. [依存関係逆転の原則（Dependency Inversion Principle: DIP）](https://qiita.com/i-tanaka730/items/993b4f6c1a701657788e#5-%E4%BE%9D%E5%AD%98%E9%96%A2%E4%BF%82%E9%80%86%E8%BB%A2%E3%81%AE%E5%8E%9F%E5%89%87dip)

## 1. 単一責任の原則（SRP）

**～クラスは1つの責務（役割）のみを持つべき～**

### 悪い例

```csharp
public class Player
{
    public void Move() {
        Console.WriteLine("プレイヤーが移動しました。");
    }
    
    public void Attack() {
        Console.WriteLine("プレイヤーが攻撃しました。");
    }
    
    public void SavePlayerData() {
        Console.WriteLine("プレイヤーデータを保存しました。");
    }
}
```

このクラスは移動・攻撃・データの保存という異なる責務を持っており、責務が混在しています。

### 良い例

```csharp
public class Player
{
    public void Move() {
        Console.WriteLine("プレイヤーが移動しました。");
    }
    
    public void Attack() {
        Console.WriteLine("プレイヤーが攻撃しました。");
    }
}

public class PlayerDataSaver
{
    public void Save(Player player) {
        Console.WriteLine("プレイヤーデータを保存しました。");
    }
}
```

責務を分けることで、変更に強い設計になります。

## 2. 開放/閉鎖の原則（OCP）

**～クラスは拡張に対して開いており、修正に対して閉じているべき～**

### 悪い例

```csharp
public class Enemy
{
    public void Attack(string type)
    {
        if (type == "Goblin")
            Console.WriteLine("ゴブリンが噛みついた！");
        else if (type == "Dragon")
            Console.WriteLine("ドラゴンが火を吹いた！");
    }
}
```

敵の種類が増えるたびに、既存コード(`Attack`メソッド)を修正する必要があます。

### 良い例

```csharp
public interface IEnemy
{
    void Attack();
}

public class Goblin : IEnemy
{
    public void Attack() => Console.WriteLine("ゴブリンが噛みついた！");
}

public class Dragon : IEnemy
{
    public void Attack() => Console.WriteLine("ドラゴンが火を吹いた！");
}

public class EnemyAttacker
{
    public void AttackEnemy(IEnemy enemy)
    {
        enemy.Attack();
    }
}
```

新しい敵を追加する際に、既存のコードを変更せずに拡張できます。

## 3. リスコフの置換原則（LSP）

**～派生クラスは基底クラスの代替として機能すべき～**

### 悪い例

```csharp
public class Character
{
    public virtual void Attack() {
        Console.WriteLine("キャラクターが攻撃！");
    }
}

public class Healer : Character
{
    public override void Attack() {
        throw new NotImplementedException("ヒーラーは攻撃できません。");
    }
}
```

`Character` 型として扱うと、`Healer` クラスで例外が発生します。派生クラスが基底クラスの動作を破壊しています。

### 良い例

```csharp
public interface ICharacter
{
    void PerformAction();
}

public class Warrior : ICharacter
{
    public void PerformAction() => Console.WriteLine("戦士が攻撃！");
}

public class Healer : ICharacter
{
    public void PerformAction() => Console.WriteLine("ヒーラーが回復！");
}
```

キャラクターの行動を `PerformAction` に統一し、期待通りの振る舞いを保証します。

## 4. インターフェース分離の原則（ISP）

**～クライアントは使用しないメソッドに依存してはならない～**

### 悪い例

```csharp
public interface ICharacter
{
    void Attack();
    void Heal();
}

public class Warrior : ICharacter
{
    public void Attack() => Console.WriteLine("戦士が攻撃！");
    public void Heal() => throw new NotImplementedException();
}
```

戦士は回復できないにも関わらず、無関係なメソッド(`Heal`メソッド)の実装を強制されてしまっています。

### 良い例

```csharp
public interface IAttacker
{
    void Attack();
}

public interface IHealer
{
    void Heal();
}

public class Warrior : IAttacker
{
    public void Attack() => Console.WriteLine("戦士が攻撃！");
}

public class Priest : IHealer
{
    public void Heal() => Console.WriteLine("僧侶が回復！");
}
```

不必要なメソッドの強制を避け、適切に分離できます。

## 5. 依存関係逆転の原則（DIP）

**～高レベルモジュールは低レベルモジュールに依存してはならない～**

### 悪い例

```csharp
public class Sword
{
    public void Use() => Console.WriteLine("剣を振った！");
}

public class Player
{
    private Sword _weapon = new Sword();
    public void Attack() => _weapon.Use();
}
```

`Player` クラスは 具体的な実装(`Sword`)に直接依存しており、拡張性が低くなっています。

### 良い例

```csharp
public interface IWeapon
{
    void Use();
}

public class Sword : IWeapon
{
    public void Use() => Console.WriteLine("剣を振った！");
}

public class Player
{
    private readonly IWeapon _weapon;
    public Player(IWeapon weapon) => _weapon = weapon;
    public void Attack() => _weapon.Use();
}
```

`IWeapon` インターフェースを導入することで、拡張性が向上します。