Java设计模式-观察者模式

什么是观察者模式,Wiki 上的定义为:

观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知
这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统

通过定义也可以看出,一个对象是可以对应多个观察者的,当这个对象发生变化时,会通知所有的观察者,让它们能够自动更新自己
观察者还是 JDK 中使用最多的模式之一,相当于定义了对象之间的一对多依赖

角色

观察者模式涉及到了4个角色:

  • 抽象目标(Subject)角色
    此抽象类提供一个界面让观察者进行添附与解附作业。通俗理解就是主要定义三个功能的方法,注册、删除、更新(通知)观察者
  • 具体目标(ConcreteSubject)角色
    将有关状态存入具体观察者对象;在具体目标的内部状态改变时,给所有登记过的观察者发出通知。
  • 抽象观察者(Observer)角色
    为所有的具体观察者定义一个接口,在得到目标(被观察者)的通知时更新自己,这个接口叫做更新接口。
  • 具体观察者(ConcreteObserver)角色
    具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与目标(被观察者)的状态相协调。如果需要,具体观察者角色可以保持一个指向具体目标对象的引用。每个观察者类别都要实做它自己的更新函式,以应对状态更新的情形。

可以看出,我们常说的“回调机制”就是它的一种体现形式嘛~~~回调的对象其实就是一个观察者嘛 (: 雾

一个栗子

下面就开始具体写这四个角色了,具体的写法不是固定的,我也看到了好多不同的写法,思路是差不多的
观察者模式的代表人物就是 MVC 了!!

抽象目标角色

首先写抽象目标角色吧,正如上面所说,主要定义的是对观察者的一些列操作,我呢,继续偷懒尽可能复用前面的代码了 o(*≧▽≦)ツ
或者你可以按照 HeadFirst 中的叫法:主题(抽象);想象成出版社?负责内容的更新以及通知相应的订阅者

1
2
3
4
5
6
7
8
9
10
public interface StandardLoli {
//注册一个观察者
void register(Lolicon observer);

//移除一个观察者
void remove(Lolicon observer);

//通知所有观察者
void notifyObservers(StandardLoliImpl data);
}

看到网上的一些其他实现是把这个类定义为抽象类,然后把上面的方法给实现了,因为所有的被观察者的实现基本都是一个套路

具体目标角色

也就是所说的 被观察者 了,我是在这里实现的抽象目标的方法,register 等方法是给 new 出的具体的对象调用的
就是上面所说的主题的具体的实现了。相当于具体的某家出版社
这个类通常会有一个属性表示状态….这里没写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class StandardLoliImpl implements StandardLoli {
// 用来保存所有的已注册的观察者
// 如果要频繁的增删那要使用 LinkedList
// 如果要多线程同步操作,要把 list 转化为安全的 Collections.synchronizedList(list);
private List<Lolicon> list = new ArrayList<>();

private String name = "佳芷";

// 此方法就当作是更新状态的方法吧
public void speak(String name) {
System.out.println("大哥哥" + name + "快来呀!!");
// 更新完后通知所有观察者
notifyObservers(this);
}

public String getName() {
return name;
}

@Override
public void register(Lolicon observer) {
list.add(observer);
}

@Override
public void remove(Lolicon observer) {
list.remove(observer);
}

@Override
public void notifyObservers(StandardLoliImpl data) {
// 通知所有的已注册的观察者更新自己
// 如果是用的 LinkedList 或者 Map 之类的要使用迭代器进行迭代
for (Lolicon lolicon : list) {
lolicon.update(data);
}
}
}

notifyObservers 的参数应该是接口的,我就是图方便直接写的实现类啦~
如果是使用的抽象类的方式,这里只要定义一个状态标志变量和 change 方法(相当于我的 speak 方法)就行了,具体的可以见参考里的第二个连接

抽象观察者角色

这个才是最简单的一个接口,就一个方法,用来更新观察者状态的

1
2
3
4
5
public interface Lolicon {
// 更新的时候可以接受一个被观察者对象,用于获取相关信息
// 这里直接传实现类了
void update(StandardLoliImpl loli);
}

观察者角色

被观察者状态变化后调用,用来通知观察者进行相应的更新自己

1
2
3
4
5
6
7
8
9
10
public class LoliconImpl implements Lolicon {
private String status = "我来了!!!";

// 当被观察者更新时会被调用
@Override
public void update(StandardLoliImpl loli) {
// 更新自己.....
System.out.println(status + loli.getName());
}
}

测试类

好了,所有的角色都定义完毕,下面就开始操练起来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainTest {
public static void main(String[] args) {
// 创建被观察者
StandardLoliImpl standardLoli = new StandardLoliImpl();

// 创建观察者
LoliconImpl lolicon = new LoliconImpl();

// 将观察者注册到被观察者上
standardLoli.register(lolicon);

// 更新被观察者,观察者也会自动更新自己的状态
standardLoli.speak("bfchengnuo");
}
}

还有一点,很多人 (反正我是…..)都不会这样严格的定义,定义四个类太麻烦啦,我嘛,简单的功能就会省略掉抽象目标角色了…
果然是不是非常像我们常用的“回调机制”

使用Java的默认实现

其实在 java 中实现观察者模式还有一种更简单的方式,它已经给我们提供好类使用了。
需要用到 java.util 包中提供的 Observable 类和 Observer 接口,分别对应 被观察者、观察者

被观察者需要继承 Observable ;它已经给你写好注册、取消等方法了,省事了不少,直接 super 调就行,不过需要注意的是 setChanged 方法,如果你不调用这个告诉它数据已经变化,notifyObservers 是不会执行的
这样其实有个好处就是避免了频繁的更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class StandardLoli extends Observable {
private String name = "佳芷";

// 此方法就当作是更新状态的方法吧
public void speak(String name) {
System.out.println("大哥哥" + name + "快来呀!!");

// 标记此 Observable 对象为已改变的对象;现在 hasChanged 方法将返回 true
super.setChanged();
// 更新完后通知所有观察者
// 如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并调用 clearChanged 方法来指示此对象不再改变。
// 还可以携带一个对象传递
super.notifyObservers();
// super.notifyObservers("data");
}

public String getName() {
return name;
}
}

Observable 内部保存多个观察者的是一个 AbstractList 集合,看一下源码应该已经保证了线程安全,但是因为它和 ArrayList 的存储结构是一样的,如果需要频繁操作还是要用链表
notifyObservers 方法可以传一个 data 数据,这相当于是“推”;如果不传就相当于“拉”;所以,Java 的实现是支持这两种方式的

然后就是观察者了,需要实现 Observer 接口,重写 update 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Lolicon implements Observer {
private String status = "我来了!!!";

public Lolicon() {}

public Lolicon(String status) {
this.status = status;
}

// 会传递过来被观察者的对象实体,和数据(如果有的话)
@Override
public void update(Observable o, Object arg) {
// 更新自己.....
StandardLoli loli = (StandardLoli) o;
System.out.println(status + loli.getName());
}
}

其实在这里的构造函数中可以接收一个被观察者,直接将本对象注册,这好像才是比较正确的使用姿势……
然后写个测试类来测试下,看看效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
public static void main(String[] args) {
// 创建被观察者
StandardLoli loli = new StandardLoli();

// 创建观察者
Lolicon lolicon1 = new Lolicon();
Lolicon lolicon2 = new Lolicon("飞奔而来!!");
Lolicon lolicon3 = new Lolicon("从天而降!!");

// 注册观察者,顺序是先加入的后执行
loli.addObserver(lolicon1);
loli.addObserver(lolicon2);
loli.addObserver(lolicon3);

// 更新被观察者状态
loli.speak("bfchengnuo");
}
}

嗯,这样看的话确实简单了不少,只需要两个类呢

使用 Java 的实现时;不要依赖于观察者被通知的次序
因为 notifyObservers 是 Java 它实现的,具体的实现思路可能和我们自己的并不相同

参考

这里推荐一篇文章:
https://www.zybuluo.com/pastqing/note/191632
http://www.cnblogs.com/java-my-life/archive/2012/05/16/2502279.html

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~