观察者模式
观察者模式是一种基于事件和响应的设计模式,常常用于传统的窗体应用程序以及游戏开发领域。一个典型的场景是,在游戏操作界面中,存在游戏角色、陷阱、怪物、宝物等,当游戏角色移动到陷阱、怪物、宝物的位置时,如何让这个移动事件能够被感知到,并作出正确响应呢?
传统的思路是,陷阱、怪物、宝物周期性地对自己的有效范围进行检测,当检测到主角时则作出响应。这种是“拉取”的思想,但存在明显的弊端,如果事件没有发生,那么程序就会一直“空转”,浪费资源;而且,即便事件有发生,如果检测周期太长,也无法得到实时的响应。
如果换成“推送”的思想,也就是把陷阱、怪物、宝物当做角色的成员变量,每当角色进入有效范围时,则调用相应对象的方法。虽然这种方式把定时“拉取”变成了实时”推送",但又引入了新问题。一是在现实意义上,陷阱、怪物、宝物并不属于游戏角色的属性,把他们当做成员变量并不恰当;二是游戏中肯定不会只有这三个元素,随着游戏内容的丰富,增加的新元素数量会越来越多,这样每次都需要往游戏角色的类添加新的成员变量,明显增加的耦合。
UML 设计
因此,采用观察者模式就能减少代码的耦合,保证对象的抽象性。具体设计包括:
- 抽象出了一个观察者接口 Observer,所有需要接收事件并作出响应的类,都需要实现这个接口。
- 被观察对象 Subject,作为一个事件发起者,维护一个观察者列表,可注册新的观察者,或者去掉旧的观察者。
观察者模式的 UML 图为:
观察者模式的 UML 图主要有两组实体对象,观察者和被观察者。观察者实现了 Observer 接口,被观察者继承自 Subject 抽象类。
Subject 抽象类依赖抽象的 Observer 接口,避免了观察者与被观察者之间的紧耦合。在 Subject 类中有一个成员变量 ObserverList,存储着已经注册的观察者,当事件发生时,就会通知列表中的所有观察者,进行各自的响应。
public interface Observer {
public void update();
}
abstract public class Subject {
private List<Observer> observerList = new ArrayList<Observer>();
public void attachObserver(Observer observer) {
observerList.add(observer);
}
public void detachObserver(Observer observer){
observerList.remove(observer);
}
public void notifyObservers(){
for (Observer observer: observerList){
observer.update();
}
}
}
游戏中的陷阱、怪物、宝物对应着 UML 图里的 ConcreteObserver 类,他们各自实现了 Observer 的接口,每一个具体的观察者都有各自的响应方法,作为事件触发的回调方法。
通俗的理解就是,游戏元素作为观察者,检测到游戏角色进入有效范围后,向被观察者(角色)进行注册,等待时间通知;被观察者执行某个动作后,则向事件对应的观察者发送通知,进行回调。
常见应用
许多游戏引擎的底层都使用了观察者模式,比如 Unity3D、Cocos2D。Spring 框架的 ApplicationListener 和 ApplicationContext 两个接口以及它们的实现类也用到了观察者模式。