Шаблон Наблюдатель (Observer) применяется в сильно интегрированных системах, где каждая часть должна знать состояние всей системы, или большей ее части (например, если нужно показать график изменений какого-либо значения без жесткого указания связи в коде или работнику нужно оповестить об изменении зарплаты соответствующие отделы).
Рассмотрим пример, когда платежная ведомость должна знать об изменении зарплаты сотрудника.
Приведем простейшую версию класса сотрудник:
Она никого ни о чем не уведомляет, эта версия просто представляет собой базовые поля объекта.
Теперь добавим простейшую реализацию слежения за изменением зарплаты - Employee будет принимать платежную ведомость и вызывать ее метод update при изменении зарплаты:
Диаграмма классов паттерна Наблюдатель (Observer)
Рассмотрим пример, когда платежная ведомость должна знать об изменении зарплаты сотрудника.
Приведем простейшую версию класса сотрудник:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#-*- encoding: utf-8 -*- | |
class Employee | |
attr_reader :name | |
attr_accessor :salary, :title | |
def initialize(name, title, salary) | |
@name = name | |
@title = title | |
@salary = salary | |
end | |
end | |
fred = Employee.new("Fred Flinstone", "Crane operator", 30000.0) | |
# Так как мы сделали attr_accessor к полю salary, можно написать | |
fred.salary = 35000.0 |
Она никого ни о чем не уведомляет, эта версия просто представляет собой базовые поля объекта.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#-*- encoding: utf-8 -*- | |
class Payroll | |
def update(changed_employee) | |
puts "Новый чек для работника #{changed_employee.name}" | |
puts "Его зарплата теперь составляет #{changed_employee.salary}!" | |
end | |
end | |
class Employee | |
attr_reader :name, :title | |
attr_accessor :title, :salary | |
def initialize(name, title, salary, payroll) | |
@name = name | |
@title = title | |
@salary = salary | |
@payroll = payroll | |
end | |
def salary=(new_salary) | |
@salary = new_salary | |
# уведомляем наблюдателя об изменении зарплаты | |
@payroll.update(self) | |
end | |
end | |
payroll = Payroll.new | |
fred = Employee.new("Fred Flinstone", "Crane operator", 30000.0, payroll) | |
fred.salary = 35000.0 | |
# Новый чек для работника Fred Flinstone | |
# Его зарплата теперь составляет 35000.0! |
Недостатком данного кода является жесткая привязка объектов. Что будет если понадобится извещать больше одного слушателя? Нам придется лезть в код Employee и менять его, что, согласитесь, очень не правильно.
Обобщим решение, создав переменную, хранящую массив наблюдателей и реализовав методы по добавлению и удалению наблюдателей:
Обобщим решение, создав переменную, хранящую массив наблюдателей и реализовав методы по добавлению и удалению наблюдателей:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#-*- encoding: utf-8 -*- | |
class Employee | |
attr_reader :name | |
attr_accessor :salary | |
def initialize(name, salary) | |
@name = name | |
@salary = salary | |
@observers = [] | |
end | |
def notify_observers | |
@observers.each do |observer| | |
observer.update_from_observable(self) | |
end | |
end | |
def add_observer(observer) | |
@observers << observer | |
end | |
def delete_observer(observer) | |
@observers.delete(observer) | |
end | |
def salary=(new_salary) | |
@salary = new_salary | |
notify_observers | |
end | |
end | |
class Payroll | |
def update_from_observable(context) | |
puts "Обновление в Payroll" | |
puts "Зарплата теперь #{context.salary}" | |
end | |
end | |
class Watcher | |
def update_from_observable(context) | |
puts "Обновление в Watcher" | |
puts "Зарплата теперь #{context.salary}" | |
end | |
end | |
fred = Employee.new("Fred", 30000.0) | |
# добавляем двух наблюдателей | |
fred.add_observer(Payroll.new) | |
fred.add_observer(Watcher.new) | |
# и изменяем зарплату | |
fred.salary = 35000.0 | |
# Обновление в Payroll | |
# Зарплата теперь 35000.0 | |
# Обновление в Watcher | |
# Зарплата теперь 35000.0 |
Так выглядит простейшая реализация паттерна с использованием модулей. Чтобы их использовать, достаточно в класс наблюдателя включить модуль Observer и реализовать метод update_from_observable(context). Context при этом является объектом наблюдаемого.
В наблюдаемый объект нужно включить модуль Observable, добавить наблюдателей с помощью метода add_observer и вызывать метод notify_observers каждый раз, когда необходимо уведомить наблюдателей об изменениях.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#-*- encoding: utf-8 -*- | |
# модуль наблюдателя | |
module Observer | |
module InstanceMethods | |
def update_from_observable(context) | |
raise "Implement method update in observer" | |
end | |
end | |
def self.included(receiver) | |
receiver.send :include, InstanceMethods | |
end | |
end | |
# модуль объекта наблюдения | |
module Observable | |
module InstanceMethods | |
def notify_observers | |
@observers.each do |observer| | |
observer.update_from_observable(self) | |
end | |
end | |
def add_observer(observer) | |
@observers ||= [] | |
@observers << observer | |
end | |
def delete_observer(observer) | |
@observers.delete(observer) | |
end | |
end | |
def self.included(receiver) | |
receiver.send :include, InstanceMethods | |
end | |
end | |
class Employee | |
attr_reader :name | |
attr_accessor :salary | |
include Observable | |
def initialize(name, salary) | |
@name = name | |
@salary = salary | |
end | |
def salary=(new_salary) | |
@salary = new_salary | |
notify_observers | |
end | |
end | |
class Payroll | |
def update_from_observable(context) | |
puts "Обновление в Payroll" | |
puts "Зарплата теперь #{context.salary}" | |
end | |
end | |
class Watcher | |
def update_from_observable(context) | |
puts "Обновление в Watcher" | |
puts "Зарплата теперь #{context.salary}" | |
end | |
end | |
fred = Employee.new("Fred", 30000.0) | |
# добавляем двух наблюдателей | |
fred.add_observer(Payroll.new) | |
fred.add_observer(Watcher.new) | |
# и изменяем зарплату | |
fred.salary = 35000.0 | |
# Обновление в Payroll | |
# Зарплата теперь 35000.0 | |
# Обновление в Watcher | |
# Зарплата теперь 35000.0 |
Комментариев нет:
Отправить комментарий