---
# System prepended metadata

title: 觀察者模式

---

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