觀察者模式做什么
觀察者模式實(shí)現(xiàn)了對象之間的多對1依賴薪介,一旦新數(shù)據(jù)發(fā)布所有訂閱者都將收到通知并自動(dòng)更新
觀察者模式核心——interface
觀察者模式-核心就是interface接口
為什么要設(shè)計(jì)接口?看個(gè)案例
現(xiàn)在有一個(gè)主題發(fā)布者Subject s,被一個(gè)訂閱者Observer1 o1訂閱嚣崭。那么偽代碼如下:
class Subject:
string topic
string content
string getTopic() { return topic}
string getContent() {return content }
void notifyAll() {
string t = getTopic()
string c = getContent()
// 調(diào)用o1的updateMyself方法更新o1
o1.updateMyself(t, c)
}
class Observer1:
void updateMyself(string topic, content) { ... }
某一天來了另外一個(gè)訂閱者Observer2 o2肥缔,新增代碼后變成:
class Subject:
string topic
string content
string getTopic() { return topic}
string getContent() {return content }
void notifyAll() {
string t = getTopic()
string c = getContent()
// 調(diào)用o1的updateMyself方法更新o1
o1.updateMyself(t, c)
// 調(diào)用o2的updateMe方法更新o2
o2.updateMe(t, c)
}
class Observer1:
void updateMyself(string topic, content) { ... }
class Observer2:
void updateMe(string topic, content) { ... }
由于Observer1和Observer2的更新方法不同芒划,每來一個(gè)訂閱者逐沙,Subject就不得不去適配該訂閱者的更新方法:
- 假設(shè)不斷地有新類型的訂閱者過來,那么就要不斷地往Subject.notifyAll里面加代碼
- 假設(shè)不斷地有訂閱者退出搏讶,那么就要不斷地往Subject.notifyAll里面刪代碼
- 假設(shè)不斷地有訂閱者修改了它們的update方法佳鳖,那么就要不斷地往Subject.notifyAll里面改代碼
- 不支持運(yùn)行時(shí)訂閱者的增減
這就是面向每個(gè)訂閱者的update()實(shí)現(xiàn)的編程,非常死板
沒有接口媒惕,就沒有協(xié)議系吩。各玩各的一套,只能是疲于適配妒蔚。因此穿挨,觀察者需統(tǒng)一實(shí)現(xiàn)update接口
只要所有觀察者將更新方法都統(tǒng)一一個(gè)函數(shù)簽名月弛,那對于Subject就非常友好了。Subject無需再關(guān)注每個(gè)訂閱者具體的更新方法科盛,因?yàn)橛嗛喺叩母路椒〒碛邢嗤暮瘮?shù)簽名帽衙。代碼如下:
class Subject:
string topic
string content
string getTopic() { return topic}
string getContent() {return content }
void notifyAll() {
string t = getTopic()
string c = getContent()
// 調(diào)用o1的update方法更新o1
o1.update(t, c)
// 調(diào)用o2的update方法更新o2
o2.update(t, c)
}
// 觀察者interface
interface Observer {
void update(string topic, content)
}
class Observer1 implements Observer:
void update(string topic, content) { ... }
class Observer2 implements Observer:
void update(string topic, content) { ... }
但是,現(xiàn)在實(shí)現(xiàn)/取消訂閱贞绵,還是通過在Subject.notifyAll()里面增加/刪除代碼來實(shí)現(xiàn)厉萝,擴(kuò)展性很差
于是,Subject還需要一個(gè)注冊訂閱者的方法榨崩,需要一個(gè)刪除訂閱者的方法:
class Subject:
string topic
string content
list observers
string getTopic() { return topic}
string getContent() {return content }
void notifyAll() {
string t = getTopic()
string c = getContent()
for each o in observers {
o.update(t, c)
}
}
void addObserver(Observer o) {
observers.add(o)
}
void removeObserver(Observer o) {
observers.remove(o)
}
// 觀察者interface
interface Observer {
void update(string topic, content)
}
class Observer1 implements Observer:
void update(string topic, content) { ... }
class Observer2 implements Observer:
void update(string topic, content) { ... }
好了谴垫,這個(gè)時(shí)候,如果這些訂閱者還想去訂閱其他subject呢母蛛?自然的思路是翩剪,其他subject的類也實(shí)現(xiàn)void addObserver(Observer o)
、void removeObserver(Observer o)
和void notifyAll()
——這又回到接口上來了彩郊。主題類也可以抽象成接口以便于擴(kuò)展:
// 主題interface
interface Subject{
void notifyAll()
void addObserver(Observer o)
void removeObserver(Observer o)
}
// 觀察者interface
interface Observer {
void update(string topic, content)
}
class ConcreteSubject implements Subject:
string topic
string content
list observers
string getTopic() { return topic}
string getContent() {return content }
void notifyAll() {
string t = getTopic()
string c = getContent()
for each o in observers {
o.update(t, c)
}
}
void addObserver(Observer o) {
observers.add(o)
}
void removeObserver(Observer o) {
observers.remove(o)
}
class Observer1 implements Observer:
void update(string topic, content) { ... }
class Observer2 implements Observer:
void update(string topic, content) { ... }
還有一個(gè)問題前弯,上面的所有例子中,訂閱者需要被通知什么信息焦辅,是通過Observer.update()的參數(shù)約定的博杖,例子中通知的數(shù)據(jù)是topic和content
如果部分訂閱者只需要topic椿胯,部分只需要content筷登,部分還需要額外的數(shù)據(jù)呢?那void update(string topic, content)就不滿足需求了
如何既保持統(tǒng)一的接口哩盲,又擁有各自的更新邏輯呢前方?——把整個(gè)"數(shù)據(jù)源"作為Observer.update()的參數(shù):
// 主題interface
interface Subject{
void notifyAll()
void addObserver(Observer o)
void removeObserver(Observer o)
string getTopic()
string getContent()
}
// 觀察者interface
interface Observer {
// 把整個(gè)"數(shù)據(jù)源"拉過來,把整個(gè)subject拿過來廉油,想要什么數(shù)據(jù)自己內(nèi)部取
void update(Subject s)
}
class ConcreteSubject implements Subject:
string topic
string content
list observers
string getTopic() { return topic}
string getContent() {return content }
void notifyAll() {
string t = getTopic()
string c = getContent()
for each o in observers {
o.update(this)
}
}
void addObserver(Observer o) {
observers.add(o)
}
void removeObserver(Observer o) {
observers.remove(o)
}
class Observer1 implements Observer:
void update(Subject s) {
string t = s.getTopic()
// update t ...
}
class Observer2 implements Observer:
void update(Subject s) {
string c = s.getContent()
// update c ...
}
小結(jié)
協(xié)商好接口(interface)惠险,所有實(shí)例都統(tǒng)一按照interface規(guī)定的標(biāo)準(zhǔn)去交互,避免了不同對象的method之間的差異抒线,便于擴(kuò)展
不同的對象按照interface去做各自的內(nèi)部實(shí)現(xiàn)班巩,它們將擁有相同的函數(shù)簽名(method signature),內(nèi)部卻是不同的函數(shù)體(method bodies, aka implementation)——暴露出去的是公共的嘶炭、統(tǒng)一的抱慌,內(nèi)部邏輯是私有的、獨(dú)占的notify無外乎兩種方式:推和拉
推:需要事先約定好observer需要的數(shù)據(jù)眨猎,subject直接推送這些數(shù)據(jù)
拉:當(dāng)不同的observer需要的數(shù)據(jù)在類型和數(shù)量上存在差異時(shí)抑进,可以通過observer拉的方式:observer調(diào)用subject暴露的方法拉取自己所需的數(shù)據(jù)