10
5

[Ruby][デザパタ]ObservableモジュールでObserverパターンを実現したい

Last updated at Posted at 2024-09-02

はじめに

ここ最近、「Head Firstデザインパターン 第2版 ―頭とからだで覚えるデザインパターンの基本」を読んでいるのですが、その中で紹介されていたObserverパターンに興味を持ち、普段業務で使用しているRubyでどのように実現できるかを考えました。本記事では、その実装方法をまとめておきます。

Observerパターンとは

Observerパターンは、ソフトウェアデザインパターンの一つで、特定のオブジェクト(Subject)の状態が変化したときに、それに依存する他のオブジェクト(Observers)に自動的に通知する仕組みを提供するパターンです。Rubyには、ObserverパターンをサポートするObservableモジュールが用意されているため、今回はこれを使って実装してみます。

Observableモジュールについて

公式リファレンスでは、以下のような説明がなされています。

Mix-in により Observer パターンを提供します。
Observable モジュールを include したクラスは Observable#changed メソッドにより更新フラグを立て、 >Observable#notify_observers が呼び出されると更新フラグが立っている場合はオブザーバに通知します (オ>>ブザーバの update メソッドを呼び出す)。 Observable#notify_observers の引数はそのままオブザーバの>update メソッドに渡されます。

このモジュールを利用すれば、Observerパターンを簡単に実現できそうです

使っていく

ここでは以下のステップで実装を進めていきます。

  1. 初期実装
  2. 機能追加による問題点
  3. Observerパターンを使った改善

それではいってみよう😃

初期実装

まずは、在庫数量の変更を通知する簡単なアプリケーションを実装します。set_quantityメソッドが呼ばれると、notify_usersメソッドによってメールとSMSで通知(今回はテキスト出力)を行います。

class Inventory
  attr_accessor :quantity

  def initialize
    @quantity = 0
    @email_notifier = EmailNotifier.new
    @sms_notifier = SmsNotifier.new
  end

  def set_quantity(quantity)
    @quantity = quantity
    notify_users
  end

  def notify_users
    @email_notifier.update(@quantity)
    @sms_notifier.update(@quantity)
  end
end

class EmailNotifier
  def update(quantity)
    puts "EmailNotifier: Inventory quantity changed to #{quantity}"
  end
end

class SmsNotifier
  def update(quantity)
    puts "SmsNotifier: Inventory quantity changed to #{quantity}"
  end
end

# 使用例
inventory = Inventory.new
inventory.set_quantity(50)

出力結果

 # EmailNotifier: Inventory quantity changed to 50
# SmsNotifier: Inventory quantity changed to 50

機能追加による弊害

一見、何の問題もないように見えますが、この設計にはいくつか問題があります。例えば、通知手段に「プッシュ通知」を追加する場合、InventoryクラスにPushNotifierを直接追加する必要があります。

class Inventory
  attr_accessor :quantity

  def initialize
    @quantity = 0
    @email_notifier = EmailNotifier.new
    @sms_notifier = SmsNotifier.new
    @push_notifier = PushNotifier.new  # 新たな通知機能を追加
  end

  def set_quantity(quantity)
    @quantity = quantity
    notify_users
  end

  def notify_users
    @email_notifier.update(@quantity)
    @sms_notifier.update(@quantity)
    @push_notifier.update(@quantity)  # 新たな通知を呼び出す
  end
end

class PushNotifier
  def update(quantity)
    puts "PushNotifier: Inventory quantity changed to #{quantity}"
  end
end

このように、通知手段が増えるたびにInventoryクラスが肥大化し、メンテナンスが困難になります。
また、各通知手段の条件を変更する場合、Inventoryクラスのコードも修正しなければならず、ロジックが複雑化します。
例えば、「在庫が10未満になったときにのみSMS通知を送る」という条件を追加する場合、以下のように変更が必要です。

class Inventory
  attr_accessor :quantity

  def initialize
    @quantity = 0
    @email_notifier = EmailNotifier.new
    @sms_notifier = SmsNotifier.new
    @push_notifier = PushNotifier.new
  end

  def set_quantity(quantity)
    @quantity = quantity
    notify_users
  end

  def notify_users
    @email_notifier.update(@quantity)
    # ここで在庫が10未満の場合のみSMS通知を送信
    @sms_notifier.update(@quantity) if @quantity < 10
    @push_notifier.update(@quantity)
  end
end

さらに、テストの複雑さも増していきます。新しい通知方法が増えるたびに、Inventoryクラスのテストも複雑化し、すべての通知方法が正しく機能しているか確認するためのテストが必要になります。
なんだか問題だらけですね。。。

Observerパターンを使って改善

それでは、Observerパターンを用いて上記の問題を改善していきましょう。

まず、InventoryクラスにObservableモジュールをインクルードし、以下のように変更します。

require 'observer'

class Inventory
  include Observable
  
  attr_accessor :quantity

  def initialize
    @quantity = 0
  end

  def set_quantity(quantity)
    @quantity = quantity
    changed
    notify_observers(quantity)
  end
end

set_quantityメソッド内で、quantityの変更を通知するためにchangedメソッドを呼び出し、その後にnotify_observersメソッドでObserversに通知を行います。
Inventoryクラスはシンプルになり、各Observerに関するロジックを一切持たない状態になります。おおお。。

次に、Observer側の実装を行います。

class EmailNotifier
  def update(quantity)
    puts "Email notification: Quantity updated to #{quantity}."
  end
end

class SmsNotifier
  def update(quantity)
    if quantity < 10
      puts "SMS notification: Quantity is low (#{quantity})."
    end
  end
end

class PushNotifier
  def update(quantity)
    puts "Push notification: Quantity updated to #{quantity}."
  end
end

Observer側の実装もシンプルで、updateメソッド内で通知処理を行うだけです。

最後に、これらを使って在庫管理のロジックを組み立てます。
以下のように実行することができます。

inventory = Inventory.new

email_notifier = EmailNotifier.new
sms_notifier = SmsNotifier.new
push_notifier = PushNotifier.new

# observerたちをobserverとして登録
inventory.add_observer(email_notifier)
inventory.add_observer(sms_notifier)
inventory.add_observer(push_notifier)

inventory.set_quantity(15)  # SMS以外の通知が送信される
inventory.set_quantity(5)   # すべての通知が送信される

このように、Observerパターンを使用することで、InventoryクラスがObserverの存在を意識することなく、柔軟に通知方法を追加・削除できるようになりました。
通知方法が増えても、Inventoryクラスの修正は不要で、メンテナンス性が向上します。また、各Observerは独立しており、必要に応じて個別にテストが可能です。

最後に

いかがでしたでしょうか、デザインパターンを施すことで、設計が改善され、拡張性がかなり向上しました。
先人の恩恵を身にしみて感じました。このような感動をもっと味わいたいので、他のデザインパターンについても引き続き勉強していきたいなと思います!
お読みいただきありがとうございました!

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5