The Observer Pattern is useful when you need to inform other objects whenever one original change of object occurs. Such as SNS feeds feature, cache clear and so on...
Without this pattern the code will looks like below:
class Payroll
def update( changed_employee )
puts("Cut a new check for #{changed_employee.name}!")
puts("His salary is now #{changed_employee.salary}!")
end
end
class Employee
attr_reader :name, :title
attr_reader :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', 'Crane Operator', 30000, payroll)
fred.salary = 35000
The trouble of the above example is that we must hard code the relation of the two classes together and when we need to add some other informed objects, we must change the Employ class.
The changing of the Employ is driven by related objects. It is really ugly.
Now if we use observer pattern, the code will be:
class Employee
attr_reader :name, :title
attr_reader :salary
def initialize( name, title, salary )
@name = name
@title = title
@salary = salary
@observers = []
end
def salary=(new_salary)
@salary = new_salary
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
fred = Employee.new('Fred', 'Crane Operator', 30000.0)
payroll = Payroll.new
fred.add_observer( payroll )
fred.salary=35000.0
By building this general mechanism, we have removed the implicit coupling between the Employee class and the Payroll object. Employee no longer cares which or how many other objects are interested in knowing about salary changes; it just forwards the news to any object that said that it was interested.
The
subject object in fact does most of the work, and the
observer objects just tell the
subject that when you ready, call me! The real observer for us in sense will be the one keeps track on the subject and the subject even does not know the observer is looking at him.
Then refactoring the subject object Employee, and extract the observable support for every subject who need it:
class 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
class Employee < Subject
attr_reader :name, :address
attr_reader :salary
def initialize( name, title, salary)
super()
@name = name
@title = title
@salary = salary
end
def salary=(new_salary)
@salary = new_salary
notify_observers
end
end
The Ruby class only have one super class, so use the Module instead, then Employee will be:
class Employee
include Subject
attr_reader :name, :address
attr_reader :salary
def initialize( name, title, salary)
super()
@name = name
@title = title
@salary = salary
end
def salary=(new_salary)
@salary = new_salary
notify_observers
end
end
The ruby use the
Observable to implement Subject module.
The update(self) is pull method, the observer pull whatever it need from the subject.
GoF also give a push method looks like
observer.update(self, :salary_changed, old_salary, new_salary)