🍍

Rubyでさらっと学ぶSOLID原則⑤「依存性逆転の原則」

2022/04/23に公開約3,500字

SOLID原則とは

ソフトウェアの拡張性や保守性を高めるための下記5つの原則のこと。

依存性逆転の原則(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はたくさんのことを知りすぎています。例えば以下に挙げることです。

  • SlackNotifierMailNotifierという名前のクラスが存在する
  • SlackNotifierMailNotifiertextを属性として持つ
  • SlackNotifierMailNotifiernotifyというメソッドを持つ

これはNotificationManagerという上位モジュールがSlackNotifierMailNotifierという下位モジュールに依存していることを表しています。つまり、依存性逆転の原則に違反しています。

この原則に違反するデメリットとして、例えばLineNotifierというクラスを新しく作ることを考えると、新しくLineNotifierを作るだけでなくNotificationManagernotify_to_lineメソッドも用意しなければならないというデメリットがあります。

解決策

NotificationManagerの中でSlackNotifierMailNotifierを呼び出すのではなく、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

NotificationManagerSlackNotifierMailNotifierというクラスが存在することを知らなくてよくなりました。notifyというメソッドに応答できるnotifierという抽象(インターフェース)を渡せば、それが何のクラスであっても問題ありません。

LineNotifierを用意しなければいけなくなった場合もLineNotifierクラスとnotifyメソッドを新しく作るだけで対応できます(NotificationManagerに変更を加える必要がなくなります)。

依存性の注入(Dependency Injection)との関係

用語が似ているので混合しがちですが、「依存性逆転の原則(DIP)」と「依存性の注入(DI)」は異なる概念です。

依存性の注入(DI)とは、解決策の例で用いた「notifierNotificationManagerのinitialize時に渡す」というテクニックのこと(つまり依存するオブジェクトを外部から注入すること)を指します。

関係性を文章で表すと「依存性の注入(DI)を使うことで依存性逆転の原則(DIP)違反を解消した」と表現できます。

参考文献

Discussion

ログインするとコメントできます