# 觀察者模式
**當一個對象狀態的改變需要改變其他對象, 或實際對像是事先未知的或動態變化的時, 可使用觀察者模式。**
## 初始版本
```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
```