在23种设计模式中,观察者模式是最常用的设计模式式之一,在程序开发中经常会用到,特别是在像Android,IOS等基于GUI的系统开发中。理解,并掌握观察者模式能够帮助我们解决很多应用开发过程中遇到的问题。
在学习每一种设计模式之前应该考虑清楚,模式的意图,解决什么问题,有什么优缺点,以及怎么合理地应用到具体的项目开发中。今天我就围绕着几个问题来讲解观察者模式。
1.观察者模式的定义
在软件系统中,存在一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变,它们之间将产生联动。为了更好地描述对象之间存在的这种一对多(包括一对一)的联动,观察者模式应运而生,它定义了对象之间一种一对多的依赖关系,让一个对象的改变能够影响其他对象。(关键词:为了实现对象之间的联动)
看了上述话,我们应该知道观察者模式要解决的问题是如何实现对象之间的联动,那么意图就是要为实现对象之间的联动提供一套完整的解决方案。
2.观察者模式的应用
在Android开发中观察者模式用的很普遍,比如我们最熟悉的给一个button设置点击监听事件,它的内部实现细节其实就是一个观察者模式的实现。除此之外我们经常在项目中写的各种回调函数,其实也是用到了观察者思想。
3.如何实现一个观察者模式
我将由简单到复杂讲解观察者模式的五种实现方式,并分析它们的优缺点,而且每一种实现方式都是对前一种实现方式的改进。话不多说,先来看第一种实现方法:
实现方式一:
这是最简单的观察者模式实现方法,下面是它的类图:
每个类的大致用途如下:
IObserver:观察者接口,它定义了观察者的更新行为
Observable: 被观察对象,与观察者接口是聚合关系,因为一个类可以被多个观察者监听。所以需要在被观察对象定义一个观察者集合,并实现增加,删除观察者和通知更新方法。
ConcreteObject:观察者的具体实现类,需要实现观察者接口,这样可以让具体观察者通过观察者接口与被观察对象解耦。
优缺点:
优点:基本可以实现对象之间的联动行为。
缺点:这样的观察者模式并不好用,因为它只适用于观察者对象和被观察对象不需要信息传递的情景。如果被观察对象通知观察者更新时还需要携带一些信息,就无法实现了。
改进方法:
虽然说这的确是个问题,但是要解决也很简单,可以在观察者接口的update方法增加一个参数,用来传递数据。为了保证传递的信息能兼容所有对象类型,可以把这个参数类型定义为Object。
实现方式二:
我们对实现方式一做一定的改进,改进后类图变成了这样子:
对观察者接口的update方法增加一个参数,基本没做什么改变。
同样来分析下它的优缺点:
优点:可以解决实现方式一的问题。
缺点:在实际开发中,我们会发现这样的观察者模式也有局限性。假如,我们想在观察者更新的时候能够拿到被观察对象的成员变量,虽然可以通过update方法的参数把被观察者对象的引用传递过来,但是这样就不可以传递一些额外的信息了,除非我们把额外数据定义成被观察对象类的成员变量。但是这样做明显是不合理的,因为可能这些数据跟被观察对象毫无关系,而你却在它的类里面定义,不提倡这样做。
改进方法:
可以在update方法多增加一个参数,把被观察对象的引用也一起传给观察者。
实现方式三:
我们继续对观察者模式进行改进,现在它的类图变成了这样子:
优缺点:
优点:解决了实现方式二的问题。
缺点:现在观察者接口变得不通用了,因为观察者接口现在依赖于一个具体的被观察对象,这样我们就必须为不同的被观察对象专门设计一个观察者接口。
改进方法:
我们来分析下为什么会有这样的问题?
其实主要原因是观察者接口依赖于具体被观察者。违背了依赖抽象编程原则。
所以可以这样改进观察者模式:
引入被观察对象的抽象类,被观察者对象继承该抽象类,让观察者接口依赖于抽象被观察者,而不依赖于具体。
实现方式四:
这是改进后的类图:
优缺点:
优点:
1.观察者接口依赖于抽象被观察者类,这样就可以复用观察者接口。
2.将增删观察者,通知更新等方法在抽象被观察者类里面实现,每个具体被观察者可以复用这部分代码,就没必要做重复实现。
缺点:
1.因为java单继承的原因,具体被观察者如果已经继承了其他类,就不能继承抽象被观察者类了,除非你在它的父类里面继承抽象被观察者类,但这也意味着该父类所有子类的实例都是一个可被观察的对象。感觉这样好像也不会有太大的问题,但是至少破坏了类的封装性,所以我们如果不想这样做的话可以选择通过接口的方式来实现观察者模式。
2.观察者对象执行更新时,想获得被观察对象的数据,需要做一个强转,把抽象被观察者类强转成具体的被观察类,这可能比较耗性能。
这样我们以后想实现一个观察者模式,只需要增加一个被观察者具体类,并继承抽象被观察者类,然后也增加一个具体观察者,并实现观察者接口,不需要更改代码,符合开闭原则。
我们再对现在的实现方式做一些细节优化,加入一些小方法。优化后的类图如下:
下面是观察者模式的具体代码实现:
AbstractObservable:
/**
* 抽象被观察者类
*/
public abstract class AbstractObservable {
private List<IObserver> observers;//观察者集合
private boolean changed=false;//标志被观察对象是否发生改变
public AbstractObservable() {
super();
observers=new ArrayList<IObserver>();
}
public void addObserver(IObserver o){
observers.add(o);
}
public void deleteObserver(IObserver o){
if(observers.size()==0){
return;
}
observers.remove(o);
}
public void notifyUpdate(){
notifyUpdate(null);
}
public void notifyUpdate(Object obj){
if(changed){
for(IObserver o:observers){
o.update(this, obj);
}
}
}
public void setChanged(boolean changed) {
this.changed = changed;
}
}
IObserver:
public interface IObserver {
/***
* 定义观察者的更新行为
* @param abstractObservable:抽象被观察者类
* @param object:传递的额外信息
*/
public void update(AbstractObservable abstractObservable,Object object);
}
ConcreteObservable:
public class ConcreteObservable extends AbstractObservable {
//写具体的业务逻辑
}
ConcreteObserver:
public class ConcreteObserver implements IObserver {
@Override
public void update(AbstractObservable abstractObservable, Object object) {
if(abstractObservable instanceof ConcreteObservable) {
//1.将抽象被观察对象强转成具体被观察对象
ConcreteObservable concreteObservable=(ConcreteObservable) abstractObservable;
//2.获取具体被观察对象的相关数据
//3.处理相关业务逻辑
}
}
}
Client:
public class Client {
public static void main(String[] args) {
AbstractObservable s=new ConcreteObservable();
IObserver o=new ConcreteObserver();
s.addObserver(o);
s.setChanged(true);//设置观察目标发生了改变
s.notifyUpdate();
}
}
jdk对观察者模式的实现
如果觉得这样的观察者模式还不够完美的话,那可以使用jdk提供的观察者模式。jdk已经为我们实现了一套完整的观察者模式框架,我们可以拿来即用。
下面是jdk观察者模式的类图:
其实跟我讲解的第四种观察者模式实现方式差不多,只不过它在细节上又做了进一步的优化。