Rubyでさらっと学ぶSOLID原則⑤「依存性逆転の原則」
SOLID原則とは
ソフトウェアの拡張性や保守性を高めるための下記5つの原則のこと。
- S(Single-responsibility principle): 単一責任の原則
- O(Open-closed principle): オープン・クローズドの原則
- L(Liskov substitution principle): リスコフの置換原則
- I(Interface segregation principle): インターフェース分離の原則
- D(Dependency inversion principle): 依存性逆転の原則
依存性逆転の原則(DIP)とは
- 上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも抽象に依存すべきである
- 抽象は実装の詳細に依存してはならない。実装の詳細が抽象に依存すべきである
という原則。
これだけでは何を言っているのかさっぱり分からないと思うので、用語を整理したあと具体例で解説します。
用語
- モジュール
- 主に「クラス」のこと
- 上位のモジュール
- 下位モジュールを使う側
- 下位のモジュール
- 上位モジュールに使われる側
- 抽象
- 静的型付け言語でよく出てくる「インターフェース」のこと
- インターフェース
- 実装の中身を持たず、メソッド名や引数の型、戻り値の型を定義したもの
依存性逆転の原則に違反している例
以下3つのクラスがあるとします。
-
NotificationManager
: 通知したあとの共通処理をさせるために用意したクラス-
SlackNotifier
: Slackへの通知を行うクラス -
MailNotifier
: メールでの通知を行うクラス
-
class NotificationManager
def initialize(text)
@text = text
end
def notify_to_slack
SlackNotifier.new(@text).notify
puts 'ここで通知後の共通処理をしたい'
end
def notify_by_mail
MailNotifier.new(@text).notify
puts 'ここで通知後の共通処理をしたい'
end
end
class SlackNotifier
def initialize(text)
@text = text
end
def notify
# Slackに通知する処理
end
end
class MailNotifier
def initialize(text)
@text = text
end
def notify
# メールで通知する処理
end
end
# Slackに通知したいとき
NotificationManager.new('おはよう').notify_to_slack
# メールで通知したいとき
NotificationManager.new('おはよう').notify_by_mail
依存性逆転の原則の用語に当てはめると、以下のとおりです。
- 上位モジュール:
NotificationManager
- 下位モジュール:
SlackNotifier
,MailNotifier
この例のNotificationManager
はたくさんのことを知りすぎています。例えば以下に挙げることです。
-
SlackNotifier
やMailNotifier
という名前のクラスが存在する -
SlackNotifier
やMailNotifier
はtext
を属性として持つ -
SlackNotifier
やMailNotifier
はnotify
というメソッドを持つ
これはNotificationManager
という上位モジュールがSlackNotifier
やMailNotifier
という下位モジュールに依存していることを表しています。つまり、依存性逆転の原則に違反しています。
この原則に違反するデメリットとして、例えばLineNotifier
というクラスを新しく作ることを考えると、新しくLineNotifier
を作るだけでなくNotificationManager
にnotify_to_line
メソッドも用意しなければならないというデメリットがあります。
解決策
NotificationManager
の中でSlackNotifier
やMailNotifier
を呼び出すのではなく、notifier
という抽象(インターフェース)を用意することで解消できます。
class NotificationManager
# `notify`メソッドに応答できる`notifier`を渡すようにする
def initialize(notifier)
@notifier = notifier
end
def notify
notifier.notify
puts 'ここで通知後の共通処理をしたい'
end
end
# Slackに通知したいとき
slack_notifier = SlackNotifier.new('おはよう')
NotificationManager.new(slack_notifier).notify
# メールで通知したいとき
mail_notifier = MailNotifier.new('おはよう')
NotificationManager.new(mail_notifier).notify
依存性逆転の原則の用語に当てはめると、以下のとおりです。
- 上位モジュール:
NotificationManager
- 抽象:
notifier
NotificationManager
はSlackNotifier
やMailNotifier
というクラスが存在することを知らなくてよくなりました。notify
というメソッドに応答できるnotifier
という抽象(インターフェース)を渡せば、それが何のクラスであっても問題ありません。
LineNotifier
を用意しなければいけなくなった場合もLineNotifier
クラスとnotify
メソッドを新しく作るだけで対応できます(NotificationManager
に変更を加える必要がなくなります)。
依存性の注入(Dependency Injection)との関係
用語が似ているので混合しがちですが、「依存性逆転の原則(DIP)」と「依存性の注入(DI)」は異なる概念です。
依存性の注入(DI)とは、解決策の例で用いた「notifier
をNotificationManager
のinitialize時に渡す」というテクニックのこと(つまり依存するオブジェクトを外部から注入すること)を指します。
関係性を文章で表すと「依存性の注入(DI)を使うことで依存性逆転の原則(DIP)違反を解消した」と表現できます。
参考文献
Author And Source
この問題について(Rubyでさらっと学ぶSOLID原則⑤「依存性逆転の原則」), 我々は、より多くの情報をここで見つけました https://zenn.dev/shuhei_takada/articles/7ea60cc7253c43著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol