【SOLID原則】単一責任の原則 とは

## はじめに

以前、SOLID 原則のであるオープンクローズドの原則について以下でまとめました。

https://qiita.com/tsubasa_k0814/items/c5295030f69460ba923d

今回は、単一責任の原則について実際のコードを交えながら説明していきます。

## 単一責任の原則

単一責任の原則は SOLID 原則の S の部分であり、英語では**Single Responsibility Principle**と呼ばれます。

クラスは 1 つの責務だけを持つべきであり、それ以上の責務を持つべきできではないというのがこの原則です。
言い換えると、クラスは一つの一つのアクター(クラスを利用するユーザーやステークホルダー)に対して
責任を持つべきということです。

### 悪い例

悪い例から見ていきましょう。

クラス図は以下のようなものになります。

```plantuml
@startuml
class Animal {
    +name: string
    +species: string
    +feed()
    +checkHealth()
}

飼育員 -> Animal
獣医 -> Animal
研究者 -> Animal



class 飼育員 {
}

class 獣医 {
}

class 研究者 {

}


@enduml
```

Animal クラスは飼育員、獣医、研究者の 3 つのアクターに対して責任を持ってますね...

さて、コードは以下の通りです。

```tsx
class Animal {
  constructor(public name: string, public species: string) {}

  // 飼育員がアクター
  feed() {
    this.getFood();
    console.log(`${this.name}に餌を与えました`);
  }

  // 獣医がアクター
  checkHealth() {
    this.getFood();
    console.log(`${this.name}の健康状態をチェックしました`);
  }

  // 研究者がアクター
  saveData() {
    console.log(`${this.name}のデータを保存しました`);
  }

  private getFood() {
    // 仕様変更前
    console.log("飼育員と獣医の共通のロジック");

    // // 仕様変更後
    // console.log("飼育員の仕様変更済み");
  }
}

function run() {
  const animal = new Animal("ライオン", "哺乳類");

  console.log("飼育員");
  animal.feed();

  console.log("");

  console.log("獣医");
  animal.checkHealth();
}

run();
```

上記を実行して出力するとコンソール上に以下の実行結果が出力されます。

```console
飼育員
飼育員と獣医の共通のロジック
ライオンに餌を与えました

獣医
飼育員と獣医の共通のロジック
ライオンの健康状態をチェックしました
```

逆に、仕様変更前のものをコメントアウトして、
仕様変更後と書かれているところをコメントアウトを解除すると...

```console
飼育員
飼育員の仕様変更済み
ライオンに餌を与えました

獣医
飼育員の仕様変更済み
ライオンの健康状態をチェックしました
```

当然ですが、獣医側で利用している `getFood()` メソッドも変更されてしまいます。
今回は、すぐ近くで利用しているかつ単純なため気づくことができすが、
実際のサービスは複雑であるため、多くの箇所で利用されている場合原因を特定するのも苦労します。

### 良い例

良い例を見ていきましょう。

```plantuml
@startuml
class Animal {
    +name: string
    +species: string

}

class Keeper {
  -getFood()
  +feed()
}

class HealthCoordinator {
  -getFood()
  +checkHealth()
}

class AnimalRepository {
  +saveData()
}


class 飼育員 {
}

class 獣医 {
}

class 研究者 {

}



Animal <-- Keeper
Animal <-- HealthCoordinator
Animal <-- AnimalRepository
Keeper <-- 飼育員
HealthCoordinator <-- 獣医
AnimalRepository <-- 研究者


@enduml
```

```tsx
class Animal {
  constructor(public name: string, public species: string) {}
}

class Keeper {
  private getFood() {
    console.log("飼育用の餌を取得するロジック");
  }

  feed(animal: Animal) {
    this.getFood();
    console.log(`${animal.name}に飼育員が餌を与えました`);
  }
}

class HealthCoordinator {
  private getFood() {
    console.log("健康を維持するための餌を取得するロジック");
  }

  checkHealth(animal: Animal) {
    this.getFood();
    console.log(`${animal.name}の健康状態をチェックしました`);
  }
}

class AnimalRepository {
  saveData(animal: Animal) {
    console.log(`${animal.name}のデータを保存しました`);
  }
}

const run = () => {
  const animal = new Animal("猫", "哺乳類");
  const keeper = new Keeper();
  const healthCoordinator = new HealthCoordinator();

  console.log("飼育員");
  keeper.feed(animal);
  console.log("");
  console.log("獣医");
  healthCoordinator.checkHealth(animal);
};

run();
```

```console
飼育員
飼育用の餌を取得するロジック
猫に飼育員が餌を与えました

獣医
健康を維持するための餌を取得するロジック
猫の健康状態をチェックしました
```

良い例では、`getFood` が独立しているので各アクターごとに他に影響がなく修正することができます。
このように、アクターごとにクラスを分割することで、単一責任の原則に違反しないより安全で変更しやすい設計ができるようになります。

## 終わりに
いかがでしたでしょうか。
今回、アドベントカレンダーということもあり SOLID 原則について
1つずつ解説していこうと思います。

また、実際にサンプルコードで原則に違反しているパターンと違反していないパターンを書くと定着も早いと思うので是非試してみてください！


## 参考資料

https://www.udemy.com/course/typescript-solid-design-patterns/?couponCode=ST20MT111124A

https://www.ogis-ri.co.jp/otc/hiroba/others/OOcolumn/single-responsibility-principle.html

https://www.amazon.co.jp/%E3%83%9F%E3%83%8E%E9%A7%86%E5%8B%95/s?k=%E3%83%9F%E3%83%8E%E9%A7%86%E5%8B%95