この記事は?
SOLIDのS(単一責任の原則)について、理解を深める為の記事です。
なるべくシンプルで、簡単に理解できるように整理していきます。
出典として、wiki様の記事を使用させていただいております。
想定読者
- 単一責任の原則がなんなのかは知ってるけど、いまいち腹落ちしてない人
責任と目的について
原則について、wikiには以下のよう記載されています。
マーティンは、責任とは変更する理由であるとし、
モジュールを変更する理由は、ひとつだけであるべきであると定めた。
モジュールを変更する理由をひとつだけにする目的は、モジュールの堅牢性を高めることである。
整理すると、
- 「責任」 とは 「変更する理由」 である
- 「モジュールの堅牢性を高めること」 が目的である
ということのようです。
コードで書いてみる
より理解を深めるために、例の部分を実際にコードで書いてみたいと思います。
周りにruby書ける人が多いので、rubyで考えてみます。
悪い例(wikiより)
例えば、報告書を作成して、描画するようなモジュールを想定する。
まず、レポートの内容を変更したい場合に、このモジュールを変更する必要がある。
また、レポートの形式を変更したい場合にも、このモジュールを変更する必要がある。
さらに、これらのふたつの変更は、それぞれ異なるアクターによるものである。
これをコードで表現してみます。
# 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クラスの実装を変更してみましょう。
# 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の例に記載されていた内容を改めて確認してみます。
まず、レポートの内容を変更したい場合に、このモジュールを変更する必要がある。
また、レポートの形式を変更したい場合にも、このモジュールを変更する必要がある。
さらに、これらのふたつの変更は、それぞれ異なるアクターによるものである。
# 記載の通り、異なるアクターによる変更が一つのモジュールに集約されてしまっていましたね。
よって、このモジュールは、変更する理由、すなわち責任を複数持っており、
これらを結合する設計は、単一責任の原則に反している。
# すなわち、その状態が”原則に反している”と言えるようです。
要すると、
複数のアクターに対する責任は、複数のモジュールに分散しましょう
という理解ができる気がします。(たぶん)
どうすれば良いか?
では原則に則って先ほどのコードを修正してみます。
# 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のサブクラスで持つようになったことで、責任が分散されたと思います。
結論
以上から「単一責任の原則」とは、
- モジュールを使用するアクターが、複数存在する場合に検討する必要がある。
- アクター毎に対する責任は、一つのモジュールに集約するのではなく、分散して実装する。
- そうすることで、他のアクターへの影響を防ぎ、モジュールの堅牢性を高めることができる。
ということだと理解しました。
理解の一助になれれば幸いです。