Использование паттерна Observer в Ruby

22 Авг
2011

Часто в различных системах, возникает необходимость совершать действия в одной части приложения при изменениях в другой. Задача стоит в информировании всех системы о изменениях в одной из ее частей. Например у нас есть пользователи со статусами, при изменении статуса требуется выполнить ряд действий: разослать сообщение другим, сохранить старый статус в историю изменений. Посмотрим как реализовать такую логику в приложении.
У нас есть класс User:
class User<br>
 attr_reader :name <br>
 attr_accessor :status<br>
 def initialize(name, status)<br>
 @name = name<br>
 @status = status<br>
 end<br>
end<br>

И есть класс News, который рассылает сообщения в системе.
class News <br>
 def update(user) <br>
 puts("#{user.name} changed his status!") <br>
 puts("His status is now #{user.status}!")<br>
 end <br>
end<br>

Теперь изменим немного класс User для того что бы он смог сообщать о изменениях в своем статусе.
class User<br>
 attr_reader :name , :status<br>
def initialize(name, status, news)<br>
 @name = name<br>
 @status = status<br>
 @news = news<br>
 end<br>
 def status=(status)<br>
 @status = status<br>
 @news. update (self)<br>
 end<br>
end<br>
class News <br>
 def update (user) <br>
 puts("#{user.name} changed his status!") <br>
 puts("His status is now #{user.status}!")<br>
 end <br>
end <br>

Проверим и получим сообщение о изменении в статусе пользователя от класса news:
news = News.new<br>
alex = User.new('Alex', 'Happy', news)<br>
alex.status = "Sad"<br>

Лучший способ информировать систему о изменениях.

Основная проблема такого подхода в том, что класс User тесно связан с классом News. Теперь например мы должны еще сохранить старый статус в истории статусов: необходимость изменять класс User при этом очень неудачна, так как сам класс и его логика на самом деле не меняется.
Лучше немного отойти от частной реализации и создать более общий интерфейс для информирования системы:
class User<br>
 attr_reader :name <br>
 attr_accessor :status<br>
 def initialize(name, status)<br>
 @name = name<br>
 @status = status<br>
 @observers = []<br>
 end<br>
 def status=(status)<br>
 @status = status<br>
 notify_observers<br>
 end<br>
 def notify_observers<br>
 @observers.each do |observer| <br>
 observer.update(self) <br>
 end<br>
 end<br>
 def add_observer(observer) <br>
 @observers << observer <br>
 end<br>
 def delete_observer(observer) <br>
 @observers.delete(observer) <br>
 end <br>
end

Теперь любой объект, который хочет узнавать о обновлениях, может быть просто добавлен как наблюдатель (observer) обьекта класса User.
news = News.new<br>
alex = User.new('Alex', 'Happy')<br>
alex.add_observer(news)<br>
alex.status = "Sad"<br>

Теперь мы избавились от неявной связи между классами User и News. Код класс User теперь не зависит от количества объектов заинтересованных в информации о изменениях объекта User. Кроме того объекты класса User могут существовать и совсем без наблюдателей (observers).
Немного рефакторинга

Допустим мы хотим пользоваться этим паттерном для различных классов и совсем не хотим повторять этот код каждый раз. Для этого выделим этот паттерн в отдельный модуль.
module Subject <br>
def initialize <br>
 @observers=[] <br>
 end <br>
def add_observer(observer) <br>
 @observers << observer <br>
 end <br>
def delete_observer(observer) <br>
 @observers.delete(observer) <br>
 end <br>
def notify_observers <br>
 @observers.each do |observer| <br>
 observer.update(self) <br>
 end<br>
end <br>
end

Теперь мы можем просто включить модуль Subject в любой класс
class User<br>
 attr_reader :name <br>
 attr_accessor :status<br>
 def initialize(name, status)<br>
 super()<br>
 @name = name<br>
 @status = statusend<br>
 end<br>
 def status=(status)<br>
 @status = status<br>
 notify_observers<br>
 end<br>
end

Observers in Rails

В рельсах реализован очень удобный механизм для наблюдения за моделями. Используя их мы можем отрефакторить код и убрать неявные связи между моделями.
Плохой пример

class Project < ActiveRecord::Base<br>
 after_create :send_create_notifications<br>
private<br>
 def send_create_notifications<br>
 self.members.each do |member|<br>
 ProjectMailer.deliver_notification(self, member)<br>
 end<br>
 end<br>
end

В этом примере мы используем callback модели для рассылки сообщений после ее создания. В данном случае лучше будет использовать observers, так как мейлер это подсистема, которая используется и другими классами.
Использование observers

class Project < ActiveRecord::Base<br>
 # nothing here<br>
end<br>
class NotificationObserver < ActiveRecord::Observer<br>
 observe Project<br>
def after_create(project)<br>
 project.members.each do |member|<br>
 ProjectMailer.deliver_notice(project, member)<br>
 end<br>
 end<br>
end

Теперь код рассылки сообщений вынесен из класса Project и намного удобнее поддерживать код почтовых оповещений в отдельном классе.
По материалам Хабрахабр.



загрузка...

Комментарии:

Наверх