1,意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知 并 被自动更新。
2,别名
依赖(Dependents),发布-订阅模式(Publish-Subscribe)
3,动机
将一个系统分割成一系列相互协作的类有一个常见的副作用,需要维护相关对象间的一致性。我们不希望为了维持一致性而使各个类紧密耦合,因为这样降低了它们的可重用性。
许多图形用户界面工具箱将用户应用的界面表示与底下的应用数据分离。 定义数据的类和负责界面表示的类可以各自独立的地服用。【题外话: 是否可以将我们现在流行的mvvm构建UI的框架和现在的这个例子类比 : 目标就是store里面的数据,观察者是什么,是各个引用了此store里面数据的组件; 目标是当前组件内state里面的data,观察者是什么,是当前组件里面绑定了该data的html元素。】 当我们修改目标数据的时候,会通知各个观察者:我们的数据发生了变化。如果观察者也就是饼状图更新了目标subject的数据 也会通知他表格。
Observer模式描述了如何建立这种关系。这一种模式中关键对象是目标(subject) 和 观察者(observer)。 一个目标可以 有任意数目的依赖它的观察者。一旦目标的状态发生了改变,所有的观察者都得到了通知。并且通知之后,观察者应该对这个通知做出响应,有的是将观察者当前自己的某个数据的状态与目标某个数据状态同步; 有的则是根据目标的通知做出响应的其他操作【mvvm 视图的更新】。
这种交互也称之为发布-订阅(publish-subscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是她的观察者。可以有任意数量的观察者订阅并接受通知。
----------------------------------------------------------------------------------------------------------
我们用typescript来实现一下这个:
代码层级:
Observer观察者和Subject目标两个基类如下:
import
Subject
from
"./Subject";
export
default
class
Observer {
name:
string
//为啥需要name属性, 目标subject移除观察者需要这个参数,你也可以设定一个唯一不变的uuid值
update(
sub:
Subject) {}
//更新操作之前,传入Subject参数(可以不需要该参数),对其进行检查,保证发出通知的目标是该observer的目标
}
import
Observer
from
'./Observer'
export
default
class
Subject {
private
observers:
Observer[]
protected
constructor(){
this.
observers = []
}
public
attach(
obj:
Observer) {
this.
observers.
push(
obj)
}
public
dettach(
obj:
Observer) {
let
index =
this.
observers.
findIndex(
item
=>
item.
name ===
obj.
name)
this.
observers.
splice(
index,
1)
}
public
getObservers () {
return
this.
observers
}
public
notify() {}
}
实现类:
import
ConcreteSubject
from
'./ConcreteSubject'
import
Observer
from
'./Observer'
export
default
class
ConcreteObserver
extends
Observer {
subject:
ConcreteSubject
name:
string
//为啥需要name属性, 目标subject移除观察者需要这个参数,你也可以设定一个唯一不变的uuid值
constructor(
object: {
sub:
ConcreteSubject,
name:
string}) {
super()
this.
subject =
object.
sub
this.
name =
object.
name
}
//更新操作之前,传入Subject参数(可以不需要该参数),对其进行检查,保证发出通知的目标是该observer的目标
update(
sub:
ConcreteSubject) {
//todo you code with new state
if(
sub ===
this.
subject)
console.
log(
`观察者
${
this.
name
}
观察的目标subject的state发生了变化`,
this.
subject.
getState())
}
}
import
Subject
from
'./Subject'
import
Observer
from
'./Observer'
export
default
class
ConcreteSubject
extends
Subject {
state:
number
constructor () {
super()
this.
state =
0
}
getState () {
return
this.
state
}
setState (
state:
number) {
let
changeStatus =
state ===
this.
state
this.
state =
state
if (!
changeStatus)
this.
notify()
}
attach(
obj:
Observer){
super.
attach(
obj)
}
dettach(
obj:
Observer){
super.
dettach(
obj)
}
notify() {
this.
getObservers().
forEach(
observer
=>{
observer.
update(
this)
})
}
}
最后的测试代码demo:
import
ConcreteSubject
from
'./ConcreteSubject'
import
ConcreteObserver
from
'./ConcreteObserver'
// step 1 目标
var
newSub =
new
ConcreteSubject()
// step 2 观察者
let
observer1 =
new
ConcreteObserver({
sub:
newSub,
name:
'o1'})
let
observer2 =
new
ConcreteObserver({
sub:
newSub,
name:
'o2'})
// step 3 将观察者观察目标
newSub.
attach(
observer1)
newSub.
attach(
observer2)
// step 4 目标变化, 观察结果
newSub.
setState(
5)
最后的运行结果是这样的:
观察者 o1 观察的目标subject的state发生了变化 5
观察者 o2 观察的目标subject的state发生了变化 5
----------------------------------------------------------------------------------------------------------
扩展:
我们还可以显示地指定感兴趣的改变。我们修改Subject的attach方法,为什么呢,比如我们只想订阅目标的某一个版块,就类似于我们微博上面关注了体育 文娱 等模块。
attach(obj: Observer, /* aspect & interest- string or something other*/) 这里的第二个参数我们扩展一下,可以将该观察者只关心于这一方面的内容。
notify的时候,我们根据 aspect & interest 来调用observer的update方法。
参考: 《设计模式:可复用面向对象软件的基础》 5.7