はじめに
ここ最近、「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パターンを簡単に実現できそうです
使っていく
ここでは以下のステップで実装を進めていきます。
- 初期実装
- 機能追加による問題点
- 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は独立しており、必要に応じて個別にテストが可能です。
最後に
いかがでしたでしょうか、デザインパターンを施すことで、設計が改善され、拡張性がかなり向上しました。
先人の恩恵を身にしみて感じました。このような感動をもっと味わいたいので、他のデザインパターンについても引き続き勉強していきたいなと思います!
お読みいただきありがとうございました!