Zer0e's Blog

设计模式之观察者模式

字数统计: 1.3k阅读时长: 5 min
2020/08/08 Share

前言

本篇谈谈观察者模式。

正文

概念

我们在开发的时候,常常会有这样的需求场景:一个对象的某个状态更新了,与这个对象相关的所有对象都要跟着更新。但这些相关对象又不能直接写在类中,这时候我们就需要一个观察者帮助我们在一个对象更新后,通知其他对象跟着更新。
常见的场景有:发布订阅模型,例如公众号的发布就属于被观察者,而用户属于观察者。再比如游戏开发中,当角色触发某个机制时,对应通知相关的增益或者负增益,这些都可以使用观察者模式来解决。

初步

最近在写的一个游戏脚本中,有这么一个场景:当角色进行攻击之后,附近如果有增益道具就会回复血量。
最简单的设计方法是设计一个事件循环,每隔一段时间判断角色是否攻击完成,并且是否判断道具是否在角色附近,但这个会形成空转,容易造成资源的浪费,并且无法进行实时的检测。
其次我还想到一种方法,那就是让把道具当作成员写进角色类,然后当角色攻击后,通知道具进行检测。代码如下:

1
2
3
4
5
6
7
public class Role {
Buff buff;
public void attack(){
System.out.println("角色攻击");
buff.update();
}
}

那这个代码的问题在于Buff类本身就不属于Role,并且如果后续需要添加更多增益效果,那就必须修改Role类,导致类与类过于耦合。
这时候我们就需要观察者模式,减少代码之间的耦合。

实现

在观察者模式中,有两个角色,观察者(Observer)与被观察者(Subject),其中被观察者中有一个List,存放着所有注册的观察者们,当被观察者更新时,通知其他观察者相应进行更新。就按照上面这个例子,我来写写代码:
我们先创建抽象类Subject,即被观察者以及创建Observer接口,即观察者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
abstract public class Subject {
private List<Observer> observerList = new ArrayList<>();
public void addObserver(Observer observer){
observerList.add(observer);
}

public void delObserver(Observer observer){
observerList.remove(observer);
}

public void notifyObserver(){
for (Observer observer : observerList){
observer.update();
}
}
}

public interface Observer {
public void update();
}

接着我们创建增益类,实现观察者接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Buff implements Observer{

public boolean inNear(){
// 判断角色是否在附近,这里逻辑就不写了
// 一般是判断半径几米
return true;
}

@Override
public void update() {
if (inNear()){
System.out.println("角色获得Buff");
}
}
}

然后创建角色类,继承抽象类被观察者Subject,

1
2
3
4
5
6
public class Role extends Subject{
public void attack(){
System.out.println("角色攻击");
notifyObserver();
}
}

接着我们需要Client类实现一个客户端,注册所有观察者:

1
2
3
4
5
6
7
8
9
10
 
public class Client {
public static void main(String[] args) {
Role role = new Role();
Buff buff = new Buff();
// 注册观察者
role.addObserver(buff);
role.attack();
}
}

我们运行查看输出:

1
2
角色攻击
角色获得Buff

这里有一点,在被观察者注册事件中,我们既可以像上文一样显式进行注册,又可以在被观察者的构造函数中就进行注册,我们只需要把观察者对象传入被观察者中,然后进行注册就可以了,算是两种不同的写法吧,上文的写法更加清楚,并且依赖关系并没有那么强。
其次我们还可以实现更多的观察者,在client中进行注册,使得多个事件可以同时进行更新。例如我们可以添加掉落物品,附近怪物会被吸引等等。

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
39
40
41
42
43
44
45
public class monster implements Observer{
public boolean isNear(){
return true;
}
@Override
public void update() {
if (isNear()){
System.out.println("附近怪物被吸引");
}
}
}
public class Treasure implements Observer{
public boolean isNear(){
return true;
}
@Override
public void update() {
if (isNear()){
System.out.println("获得宝箱");
}
}
}

public class Client {
public static void main(String[] args) {
Role role = new Role();
Buff buff = new Buff();
Treasure treasure = new Treasure();
Monster monster = new Monster();
// 注册观察者
role.addObserver(buff);
role.addObserver(treasure);
role.addObserver(monster);
role.attack();
}
}

/*
System out:
角色攻击
角色获得Buff
获得宝箱
附近怪物被吸引
*/

优缺点

使用观察者模式的优点就是解耦,使对象之间的关系弱化,并且可以统一触发机制。
而缺点则是被观察者如果有许多观察者的话,通知所有的观察者可能需要花费很多时间。并且由于一个类既可以是观察者也可以是被观察者,如果有循环依赖的话,会导致不断调用。其次观察者模式只能知道目标发生了改变,但却无法知道怎么发生变化的。

后言

本文总结了观察者模式,写这个模式是因为最近在写的一个脚本中出现了类似的需求,就先把这个模式整理重新学习了一遍。

CATALOG
  1. 1. 前言
  2. 2. 正文
    1. 2.1. 概念
    2. 2.2. 初步
    3. 2.3. 实现
    4. 2.4. 优缺点
  3. 3. 后言