Часто в различных системах, возникает необходимость совершать действия в одной части приложения при изменениях в другой. Задача стоит в информировании всех системы о изменениях в одной из ее частей. Например у нас есть пользователи со статусами, при изменении статуса требуется выполнить ряд действий: разослать сообщение другим, сохранить старый статус в историю изменений. Посмотрим как реализовать такую логику в приложении.
У нас есть класс User:
И есть класс News, который рассылает сообщения в системе.
Теперь изменим немного класс User для того что бы он смог сообщать о изменениях в своем статусе.
Проверим и получим сообщение о изменении в статусе пользователя от класса news:
Основная проблема такого подхода в том, что класс User тесно связан с классом News. Теперь например мы должны еще сохранить старый статус в истории статусов: необходимость изменять класс User при этом очень неудачна, так как сам класс и его логика на самом деле не меняется.
Лучше немного отойти от частной реализации и создать более общий интерфейс для информирования системы:
Теперь любой объект, который хочет узнавать о обновлениях, может быть просто добавлен как наблюдатель (observer) обьекта класса User.
Теперь мы избавились от неявной связи между классами User и News. Код класс User теперь не зависит от количества объектов заинтересованных в информации о изменениях объекта User. Кроме того объекты класса User могут существовать и совсем без наблюдателей (observers).
Допустим мы хотим пользоваться этим паттерном для различных классов и совсем не хотим повторять этот код каждый раз. Для этого выделим этот паттерн в отдельный модуль.
Теперь мы можем просто включить модуль Subject в любой класс
В рельсах реализован очень удобный механизм для наблюдения за моделями. Используя их мы можем отрефакторить код и убрать неявные связи между моделями.
В этом примере мы используем callback модели для рассылки сообщений после ее создания. В данном случае лучше будет использовать observers, так как мейлер это подсистема, которая используется и другими классами.
Теперь код рассылки сообщений вынесен из класса Project и намного удобнее поддерживать код почтовых оповещений в отдельном классе.
У нас есть класс User:
class User
attr_reader :name
attr_accessor :status
def initialize(name, status)
@name = name
@status = status
end
end
И есть класс News, который рассылает сообщения в системе.
class News
def update(user)
puts("#{user.name} changed his status!")
puts("His status is now #{user.status}!")
end
end
Теперь изменим немного класс User для того что бы он смог сообщать о изменениях в своем статусе.
class User
attr_reader :name , :status
def initialize(name, status, news)
@name = name
@status = status
@news = news
end
def status=(status)
@status = status
@news. update (self)
end
end
class News
def update (user)
puts("#{user.name} changed his status!")
puts("His status is now #{user.status}!")
end
end
Проверим и получим сообщение о изменении в статусе пользователя от класса news:
news = News.new
alex = User.new('Alex', 'Happy', news)
alex.status = "Sad"
Лучший способ информировать систему о изменениях.
Основная проблема такого подхода в том, что класс User тесно связан с классом News. Теперь например мы должны еще сохранить старый статус в истории статусов: необходимость изменять класс User при этом очень неудачна, так как сам класс и его логика на самом деле не меняется.
Лучше немного отойти от частной реализации и создать более общий интерфейс для информирования системы:
class User
attr_reader :name
attr_accessor :status
def initialize(name, status)
@name = name
@status = status
@observers = []
end
def status=(status)
@status = status
notify_observers
end
def notify_observers
@observers.each do |observer|
observer.update(self)
end
end
def add_observer(observer)
@observers << observer
end
def delete_observer(observer)
@observers.delete(observer)
end
end
Теперь любой объект, который хочет узнавать о обновлениях, может быть просто добавлен как наблюдатель (observer) обьекта класса User.
news = News.new
alex = User.new('Alex', 'Happy')
alex.add_observer(news)
alex.status = "Sad"
Теперь мы избавились от неявной связи между классами User и News. Код класс User теперь не зависит от количества объектов заинтересованных в информации о изменениях объекта User. Кроме того объекты класса User могут существовать и совсем без наблюдателей (observers).
Немного рефакторинга
Допустим мы хотим пользоваться этим паттерном для различных классов и совсем не хотим повторять этот код каждый раз. Для этого выделим этот паттерн в отдельный модуль.
module Subject
def initialize
@observers=[]
end
def add_observer(observer)
@observers << observer
end
def delete_observer(observer)
@observers.delete(observer)
end
def notify_observers
@observers.each do |observer|
observer.update(self)
end
end
end
Теперь мы можем просто включить модуль Subject в любой класс
class User
attr_reader :name
attr_accessor :status
def initialize(name, status)
super()
@name = name
@status = statusend
end
def status=(status)
@status = status
notify_observers
end
end
Observers in Rails
В рельсах реализован очень удобный механизм для наблюдения за моделями. Используя их мы можем отрефакторить код и убрать неявные связи между моделями.
Плохой пример
class Project < ActiveRecord::Base
after_create :send_create_notifications
private
def send_create_notifications
self.members.each do |member|
ProjectMailer.deliver_notification(self, member)
end
end
end
В этом примере мы используем callback модели для рассылки сообщений после ее создания. В данном случае лучше будет использовать observers, так как мейлер это подсистема, которая используется и другими классами.
Использование observers
class Project < ActiveRecord::Base
# nothing here
end
class NotificationObserver < ActiveRecord::Observer
observe Project
def after_create(project)
project.members.each do |member|
ProjectMailer.deliver_notice(project, member)
end
end
end
Теперь код рассылки сообщений вынесен из класса Project и намного удобнее поддерживать код почтовых оповещений в отдельном классе.