# 觀察者模式 **當一個對象狀態的改變需要改變其他對象, 或實際對像是事先未知的或動態變化的時, 可使用觀察者模式。** ## 初始版本 ```ruby= class Professor attr_reader :name, :exam_date def initialize(name, subject, stu, assistant, dean) @name = name @subject = subject @stu = stu @assistant = assistant @dean = dean end def set_midterm(midterm_date) @exam_date = midterm_date #必須知道每一個class代表意義 -> 教授知道太多了 @stu.update(self) @assistant.update(self) @dean.update(self) end end class Student def update(prof) puts "start studying for Prof. #{prof.name} exam on the #{prof.exam_date}" end end class Assistant def update(prof) puts "find a classroom for Prof. #{prof.name} exam on the #{prof.exam_date}" end end class Dean def update(prof) puts "go over for Prof. #{prof.name} exam on the #{prof.exam_date}" end end stu = Student.new ass = Assistant.new dean = Dean.new prof = Professor.new("Jeff", "Software", stu, ass, dean) prof.set_midterm("2020-03-22") ``` 遇到問題:如果教授開很多課,並且設立很多期中考怎辦? 每一個課程的對象並不會一樣(有本校生/外校生/旁聽生) 這樣的設計會導致教授需要知道更多才能 -> 耦合嚴重(違反物件導向) ## 改善方法:把需要通知的人成為一個陣列 ```ruby= class Professor attr_reader :name, :exam_date def initialize(name, subject) @name = name @subject = subject @observers = []#我只要知道observers就好 end def add_observer(observer) @observers << observer end def set_midterm(midterm_date) @exam_date = midterm_date # 确定时间后,调用(通知)学生、 notify_observers end def notify_observers @observers.each do |observer| observer.update(self) end end end ``` ```ruby= #意味者誰訂閱,誰就是observers的一員 prof = Professor.new("Jeff", "Software") stu = Student.new prof.add_observer(stu) ass = Assistant.new prof.add_observer(ass) dean = Dean.new prof.add_observer(dean) prof.set_midterm("2020-03-22") ``` ## 擴充:若是一個教授多個課程 -> 責任分離(class應該是subject) ```ruby= #這邊設為module的原因在於,課程應該教授其中一個“附件” #不應該設立同位的class module Subject def add_observer(observer) @observers = [] unless defined? @observers @observers << observer end def notify_observers @observers = [] unless defined? @observers @observers.each do |observer| observer.update(self) end end end class Professor include Subject attr_reader :name, :exam_date def initialize(name, subject) @name = name @subject = subject end def set_midterm(midterm_date) @exam_date = midterm_date # 确定时间后,调用(通知)学生、助理和系主任 notify_observers end end ``` ### 為觀察者模式增加功能 ```ruby= 1.add_observer(obj, func=:update) 将对象obj添加为订阅者,func为notify_observers时调用的方法, 默认为update方法即obj.func必须是可响应的(respond_to) 2.changed(state=true) 设置状态。当changed为true时,意味着应使用notify_observers通知订阅者 3.changed? 返回changed状态 4.count_observers() 返回当前订阅者的数量 5.delete_observer(observer) 删除某个订阅者 6.delete_observers() 删除所有订阅者 7.notify_observers(*arg) 通知订阅者,即调用订阅者对象的update方法(默认为该方法), 同时传递arg参数给update。通知完成后,将自动设置changed=false。 注意,该方法会检查changed状态,只有为true时才会通知 ``` ```ruby= require 'observer' class Professor include Observable attr_reader :name, :exam_date def initialize(name, subject) @name = name @subject = subject end def set_midterm(midterm_date) @exam_date = midterm_date # 确定时间后,调用(通知)学生、助理和系主任 changed notify_observers(self) end end ``` ### 將功能寫成額外的module ```ruby= module Observable def add_observer(observer, func=:update) @observer_peers = {} unless defined? @observer_peers # 指定的调用函数必须能响应 unless observer.respond_to? func raise NoMethodError, "observer does not respond to `#{func}'" end @observer_peers[observer] = func end # 从@observer_peers中删除observer def delete_observer(observer) @observer_peers.delete observer if defined? @observer_peers end # 删除@observer_peers中所有observer def delete_observers @observer_peers.clear if defined? @observer_peers end # 统计observer的个数 def count_observers if defined? @observer_peers @observer_peers.size else 0 end end # 设置changed状态,默认状态为true def changed(state=true) @observer_state = state end def changed? if defined? @observer_state and @observer_state true else false end end # 通过遍历@observer_peers,通知所有的observer并调用它们的调用函数 # 要求changed状态为true,同时设置changed状态为false def notify_observers(*arg) if defined? @observer_state and @observer_state if defined? @observer_peers @observer_peers.each do |k, v| k.send v, *arg end end @observer_state = false end end end ``` 唯一問題,因為module是更動整個部分,所以無法對單一訂閱者進行變更 更動方法: ```ruby= module Observable # 区分名称:notify_observer()和notify_observers() def notify_observer(observer, *arg) if defined? @observer_peers raise "#{observer} is not a observer" unless @observer_peers.include? observer observer.send @observer_peers[observer], *arg end end end ``` ```ruby= def set_midterm(midterm_date) @exam_date = midterm_date @observer_peers.each do |k, _| notify_observer(k, self) end end ```