SOLIDのS(単一責任の原則)について

# この記事は？
SOLIDのS(単一責任の原則)について、理解を深める為の記事です。
なるべくシンプルで、簡単に理解できるように整理していきます。

出典として、wiki様の記事を使用させていただいております。

https://ja.wikipedia.org/wiki/%E5%8D%98%E4%B8%80%E8%B2%AC%E4%BB%BB%E3%81%AE%E5%8E%9F%E5%89%87

# 想定読者
- 単一責任の原則がなんなのかは知ってるけど、いまいち腹落ちしてない人

# 責任と目的について

原則について、wikiには以下のよう記載されています。

```
マーティンは、責任とは変更する理由であるとし、
モジュールを変更する理由は、ひとつだけであるべきであると定めた。
モジュールを変更する理由をひとつだけにする目的は、モジュールの堅牢性を高めることである。
```

整理すると、

1. 「責任」 とは 「変更する理由」 である
1. 「モジュールの堅牢性を高めること」 が目的である

ということのようです。

# コードで書いてみる

より理解を深めるために、例の部分を実際にコードで書いてみたいと思います。
周りにruby書ける人が多いので、rubyで考えてみます。

## 悪い例（wikiより）
```
例えば、報告書を作成して、描画するようなモジュールを想定する。

まず、レポートの内容を変更したい場合に、このモジュールを変更する必要がある。
また、レポートの形式を変更したい場合にも、このモジュールを変更する必要がある。
さらに、これらのふたつの変更は、それぞれ異なるアクターによるものである。
```

これをコードで表現してみます。

```ruby
# Reportクラス
# 報告書を作成して、描画するモジュール
# レポートの内容を変更したい場合に、このモジュールを変更する必要がある。
# レポートの形式を変更したい場合にも、このモジュールを変更する必要がある。
class Report
    # レポートを作成する
    def initialize(contents)
        @contents = contents
    end

    # レポートを描画する
    def render
        render_report @contents
    end

    private

    # 描画する処理
    def render_report(contents)
        # do render
    end
end

# クライアント向けレポートを生成するクラス(Actor)
class ClientReporter
    def report(text)
        Report.new(text).render
    end
end

# ユーザー向けレポートを生成するクラス(Actor)
class UserReporter
    def report(text)
        Report.new(text).render
    end
end
```


## 何がダメなのか？

wikiには以下のように記載されています。
```
よって、このモジュールは、変更する理由、すなわち責任を複数持っており、
これらを結合する設計は、単一責任の原則に反している。

仮に、レポートの内容の変更に伴って、報告書を作成する実装を修正する場合、
報告書を描画する処理にも影響が及ぶ可能性がある。
```

ちょっと日本語が難しいですね。実際にどんな状態かコードと共に考えてみます。
例では「レポートの内容の変更に伴って」と記載されているので、ここでは以下のような追加要件があったと場合を想定して考えてみます。

- ユーザー向けレポートの場合、フッターにオススメ情報メッセージを追加する

上記要件を追加するため、Reportクラスの実装を変更してみましょう。

```ruby
# Reportクラス
class Report
    def initialize(contents)
        @contents = contents
    end

    # レポートを描画する
    def render
        render_report(@contents + get_recommendations) # オススメ情報フッターを追加する
    end

    private

    # 描画する処理
    def render_report(contents)
        # do render
    end

    # オススメ情報を取得
    def get_recommendations
        # do get recommentations
    end
end
```

無事ユーザー向けレポートの場合、フッターを追加する機能を実装することができました。

しかし、どうでしょうか？
これでは、クライアント向けのレポートにもオススメ情報が表示されるようになってしまいました。

ここでwikiの例に記載されていた内容を改めて確認してみます。

```bash
まず、レポートの内容を変更したい場合に、このモジュールを変更する必要がある。
また、レポートの形式を変更したい場合にも、このモジュールを変更する必要がある。
さらに、これらのふたつの変更は、それぞれ異なるアクターによるものである。
# 記載の通り、異なるアクターによる変更が一つのモジュールに集約されてしまっていましたね。

よって、このモジュールは、変更する理由、すなわち責任を複数持っており、
これらを結合する設計は、単一責任の原則に反している。
# すなわち、その状態が”原則に反している”と言えるようです。
```

要すると、
**複数のアクターに対する責任は、複数のモジュールに分散しましょう**
という理解ができる気がします。（たぶん）

## どうすれば良いか？

では原則に則って先ほどのコードを修正してみます。

```ruby
# Report抽象クラス
class AbsReport
    def initialize(contents)
        @contents = contents
    end

    # レポートを描画する
    def render
        raise "Don't call this method directly."
    end
end

# Client向けReporter
class ClientReport < AbsReport
    def render
        # client向けレンダー処理
    end    
end

# User向けReporter
class UserReport < AbsReport
    def render
        # user向けレンダー処理
    end    
end
```

こんな感じでしょうか。
各アクターへの責任が、別のReportのサブクラスで持つようになったことで、責任が分散されたと思います。

# 結論

以上から「単一責任の原則」とは、
- **モジュールを使用するアクターが、複数存在する場合に検討する必要がある。**
- **アクター毎に対する責任は、一つのモジュールに集約するのではなく、分散して実装する。**
- **そうすることで、他のアクターへの影響を防ぎ、モジュールの堅牢性を高めることができる。**

ということだと理解しました。
理解の一助になれれば幸いです。 :pray: