观察者模式
上一篇文章中我们介绍了策略模式,这一篇我们主要来介绍一下观察者模式。
1、定义
观察者模式 定义了对象之间一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
观察者模式的定义很好理解,定义了一对多的依赖,当一个对象发生改变时,依赖它的对象都能够收到通知并执行相应的更新操作,下面我们通过一个案例来详细讲解一下观察者模式。
2、观察者模式案例
2.1、需求
假设我们做了一个类似于微信的软件,这个软件中有多个用户,用户可以自行关注一些订阅号,当某个订阅号更新的时候,关注该订阅号的用户都能够收到相关消息,用户也可以取消关注订阅号,用户取消后将不再收到订阅号的消息。
2.2、第一种实现方式
看到这个需求后我们最先想到的实现方式是:先定义一系列的用户类,每个用户类中都有更新消息的方法,然后再定义一个订阅号类,在订阅号类中引入用户类,只有关注了此公众号的用户类才会被引入,当订阅号有新消息时调用每个用户类的更新方法即可。下面我们就是用代码来实现这个想法,我们首先定义几个用户类,假设这个软件中有三个用户:普通用户、企业用户和VIP用户:
/**普通用户*/
public class GeneralUser {
public void update(String message) {
System.out.println("普通用户收到消息:" + message);
}
}
/**企业用户*/
public class EnterpriseUser {
public void update(String message) {
System.out.println("企业用户收到消息:" + message);
}
}
/**VIP用户*/
public class VipUser {
public void update(String message) {
System.out.println("会员收到消息:" + message);
}
}
这里每个用户都有一个更新方法update(),现在我们再定义一个订阅号类:
public class Subscription {
String message;
public void sendMessage() {
GeneralUser generalUser = new GeneralUser();
EnterpriseUser enterpriseUser = new EnterpriseUser();
VipUser vipUser = new VipUser();
generalUser.update(message);
enterpriseUser.update(message);
vipUser.update(message);
}
}
这个类中只定义了一个属性message表示消息、一个发送消息方法sendMessage(),当订阅号更新后会调用sendMessage()来给用户发送消息,在sendMessage()方法中调用了各个用户的更新方法update()用于收取消息。接下来我们就可以写一个测试方法进行测试了:
public static void main(String[] args) {
Subscription subscription = new Subscription();
subscription.message = "测试消息";
subscription.sendMessage();
}
运行结果:
普通用户收到消息:测试消息
企业用户收到消息:测试消息
会员收到消息:测试消息
可以看到,当订阅号有更新后,用户都收到了消息,这样的实现方式看似实现了需求中规定的功能,但实际上这种方式是存在问题的:
- 如果软件用户增加了,比如新来了一位黄金会员关注了此订阅号,那么我们就需要修改Subscription的sendMessage()方法来让黄金会员也能够收到消息,如果再来一位铂金会员,又需要添加新的用户信息,这样每次新增一位关注用户都需要修改一次Subscription的sendMessage()方法,这样代码会越来越臃肿,而且订阅号与用户之间紧紧耦合在一起,难以维护;
- 需求中提到,用户可以取消关注,取消关注后将不再收到消息,在上述的实现方式中要使用户不再收到消息,唯一的办法就是删除Subscription的sendMessage()方法中相关用户信息,还是需要改动程序。
总的来说,上述1、2两点描述的是相同的问题,上述实现方式无法实现动态地增删用户,每次增加和删除用户时都需要手动修改Subscription的sendMessage()方法,这样使代码的可扩展性变得很差,要解决这个问题我们可以使用观察者模式来重新设计一下。
2.3、观察者模式实现方式
现在我们通过观察者模式的定义来分析一下如何实现需求,观察者模式定义了对象之间一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。在这个案例中,订阅号的消息就类似于对象,而关注订阅号的用户就相当于对象的依赖者,每次订阅号更新的时候,用户都能收到消息并进行更新,这样我们就可以定义一个类Subscription表示订阅号,其中有通知用户的功能。另外,需求中还提到用户可以关注和取消关注,也就是说订阅号类中需要有新增和删除用户的功能。我们可以将订阅号类中的方法抽象为一个接口Subject,其中包含新增、删除观察者功能,还有通知观察者功能,这样使程序有更好的扩展性。
现在观察对象已经定义好了,我们可以定义对象的依赖者了,同样的,我们可以将依赖者抽象为一个接口Observer,这个接口中只有一个更新方法,每当对象状态变化时,对象的依赖者都可以执行更新方法。而这个需求中的用户都需要实现这个Observer接口,用于订阅号更新后收取消息。下图是我们基于此思路新设计的类图:
在类图中我们定义了两个接口Subject和Observer,其中Subject为观察对象,它可以新增和删除观察者,还可以在状态改变时通知所有的观察者,订阅号类Subscription就是Subject的实现类。Observer为观察者,里面只有一个更新方法,用于观察对象状态改变时进行更新。所有的用户类都实现Observer接口,这样当Subscription类状态改变时,所有的用户类都能够收到消息。接下来我们就编码实现这个功能,首先是定义两个接口:
/**观察对象*/
public interface Subject {
public void addObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyAllObservers();
}
/**观察者*/
public interface Observer {
public void update(Object o);
}
在观察对象接口中我们定义了新增方法addObserver()和删除方法removeObserver(),这两个方法都需要一个观察者作为参数,这个观察者是用来新增和删除的。当观察对象状态改变时,我们可以使用notifyAllObservers()方法来通知所有的观察者。观察者接口中我们只定义了一个update()方法,这个方法接受一个Object类型的参数,其实upate()可以接受任意类型的参数,也可以不接收任意参数,这个可以根据更新时是否需要某些信息来确定。接下来我们就可以实现这两个接口了,我们先定义一下订阅号类:
public class Subscription implements Subject {
List<Observer> observerList = new ArrayList<Observer>();
String message;
@Override
public void addObserver(Observer observer) {
observerList.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observerList.remove(observer);
}
@Override
public void notifyAllObservers() {
for(Observer o : observerList) {
o.update(message);
}
}
public void sendMessage(String message) {
this.message = message;
notifyAllObservers();
}
}
可以看到,在这个类中我们使用List来记录观察者,每次新增和删除观察者都是操作这个List,在通知所有观察者方法中我们遍历每一个观察者,然后通过调用Observer的update()方法来完成观察者的更新操作。另外,我们在Subscription类中定义了一个独有的方法sendMessage(),这个方法其实是用来模拟发送消息的,每次发送消息的时候我们都会通知所有的观察者也就是关注此订阅号的用户。最后我们就是定义用户类了:
/**普通用户*/
public class GeneralUser implements Observer {
Subject subject;
public GeneralUser(Subject subject) {
this.subject = subject;
subject.addObserver(this);
}
@Override
public void update(Object o) {
System.out.println("普通用户收到消息:" + o);
}
}
/**企业用户*/
public class EnterpriseUser implements Observer {
Subject subject;
public EnterpriseUser(Subject subject) {
this.subject = subject;
subject.addObserver(this);
}
@Override
public void update(Object o) {
System.out.println("企业用户收到消息:" + o);
}
}
/**VIP用户*/
public class VipUser implements Observer {
Subject subject;
public VipUser(Subject subject) {
this.subject = subject;
subject.addObserver(this);
}
@Override
public void update(Object o) {
System.out.println("会员收到消息:" + o);
}
}
可以看到,我们在每个用户类中都定义了一个Subject属性,并在构造函数中将用户添加进观察对象中,这样每次观察对象更新时用户都能够收到消息并进行更新了。现在所有的功能我们都已经实现了,我们可以来测试一下:
public static void main(String[] args) {
Subscription subscription = new Subscription();
GeneralUser generalUser = new GeneralUser(subscription);
EnterpriseUser enterpriseUser = new EnterpriseUser(subscription);
VipUser vipUser = new VipUser(subscription);
subscription.sendMessage("测试消息");
System.out.println("普通用户离开了");
subscription.removeObserver(generalUser);
subscription.sendMessage("最新消息");
}
在这个测试方法中,我们先定义了订阅号类,然后定义三个用户分别关注了订阅号,这样当订阅号发送消息时,三个用户都可以收到消息。我们还使用了Subject的removeObserver()模拟普通用户取消关注,这样,普通用户取消关注后将不再收到订阅号的消息,程序的执行结果如下:
普通用户收到消息:测试消息
企业用户收到消息:测试消息
会员收到消息:测试消息
普通用户离开了
企业用户收到消息:最新消息
会员收到消息:最新消息
同样的,如果我们想新增一个用户,只需要在测试方法中新增一个实现了Observer接口的对象即可,可以看到,观察者模式很好地解决了无法动态地增删用户的问题,而且观察对象与观察者之间不是紧耦合的,它们通过接口进行交互,使对象之间的依赖性降到最低,程序的可扩展性大大提升了。
2.4、使用Java内置的观察者模式
其实,Java API提供了内置的观察者模式,它们是java.util包下的Observable类和Observer接口,其实它们和上面我们定义的Subject和Observer是类似的,Observable作为观察对象而Observer作为观察者。我们先简单看一下Observable和Observer的源码:
/**Observable类*/
public class Observable {
private boolean changed = false;
private Vector<Observer> obs = new Vector();
public synchronized void addObserver(Observer arg0) {
if (arg0 == null) {
throw new NullPointerException();
} else {
if (!this.obs.contains(arg0)) {
this.obs.addElement(arg0);
}
}
}
public synchronized void deleteObserver(Observer arg0) {
this.obs.removeElement(arg0);
}
public void notifyObservers() {
this.notifyObservers((Object) null);
}
public void notifyObservers(Object arg0) {
Object[] arg1;
synchronized (this) {
if (!this.changed) {
return;
}
arg1 = this.obs.toArray();
this.clearChanged();
}
for (int arg2 = arg1.length - 1; arg2 >= 0; --arg2) {
((Observer) arg1[arg2]).update(this, arg0);
}
}
public synchronized void deleteObservers() {
this.obs.removeAllElements();
}
protected synchronized void setChanged() {
this.changed = true;
}
protected synchronized void clearChanged() {
this.changed = false;
}
public synchronized boolean hasChanged() {
return this.changed;
}
public synchronized int countObservers() {
return this.obs.size();
}
}
/**Observer接口*/
public interface Observer {
void update(Observable arg0, Object arg1);
}
可以看到,Observable和我们自己定义的Subject接口是类似的,它也定义了新增观察者方法addObserver()和删除观察者方法deleteObserver(),另外,这个类中还定义了两个通知观察者方法notifyObservers()和notifyObservers(Object arg),其实它们是功能是一样的,只是notifyObservers(Object arg)在通知观察者时可以传送数据对象给每个观察者。另外值得注意的是,这个Observable类中还定义了一个setChanged()方法,这个方法用于标记状态已经改变,Observable类中通知方法和我们定义的通知方法有一点不同在于只有状态被标记为改变时才会通知观察者,因此要使用Observable通知观察者时我们首先需要调用setChanged()方法将状态标记为已改变,然后才能调用notifyObservers()方法通知观察者,如果不调用setChanged()方法,直接调用notifyObservers()方法是不会通知观察者的。
JDK中定义的Observer接口和我们自己定义的Observer接口是相同的,只有一个update()方法,不过这个方法接收两个参数,Observable用于让观察者确认是由哪个观察对象通知观察者的,Object就是Observable的通知方法notifyObservers(Object arg)传送过来的数据对象。现在我们看一下如何使用Java内置的观察者模式实现我们的需求。
首先我们定义一个订阅号类Subscription:
public class Subscription extends Observable {
public void sendMessage(String message) {
setChanged();
notifyObservers(message);
}
}
可以看到,Subscription类是继承自Observable的,因为Observable类中已经实现了新增、删除和通知观察者的功能,所以我们在Subscription类中无需自行实现这些功能,我们只需要写一个发送消息的方法sendMessage()即可,在这个方法中我们首先将状态标记为已改变,然后才调用通知观察者方法。
接着我们需要定义几个用户类:
/**普通用户*/
public class GeneralUser implements Observer{
Observable observable;
public GeneralUser(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
@Override
public void update(Observable observable, Object o) {
if(observable instanceof Subscription) {
System.out.println("普通用户收到消息:" + o);
}
}
}
/**企业用户*/
public class EnterpriseUser implements Observer {
Observable observable;
public EnterpriseUser(Observable ovservable) {
this.observable = ovservable;
observable.addObserver(this);
}
@Override
public void update(Observable observable, Object o) {
if(observable instanceof Subscription) {
System.out.println("企业用户收到消息:" + o);
}
}
}
/**VIP用户*/
public class VipUser implements Observer {
Observable observable;
public VipUser(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
@Override
public void update(Observable observable, Object o) {
if(observable instanceof Subscription) {
System.out.println("会员收到消息:" + o);
}
}
}
这几个用户类的定义和我们自行设计的观察者模式中用户类定义是基本相同的,在这里我们就不在赘述了。最后我们来写一个测试方法测试我们的功能:
public static void main(String[] args) {
Subscription subscription = new Subscription();
GeneralUser generalUser = new GeneralUser(subscription);
EnterpriseUser enterpriseUser = new EnterpriseUser(subscription);
VipUser vipUser = new VipUser(subscription);
subscription.sendMessage("最新消息");
System.out.println("会员离开了");
subscription.deleteObserver(vipUser);
subscription.sendMessage("群发消息");
}
运行结果:
会员收到消息:最新消息
企业用户收到消息:最新消息
普通用户收到消息:最新消息
会员离开了
企业用户收到消息:群发消息
普通用户收到消息:群发消息
可以看到每当订阅号更新的时候,关注此订阅号的用户都能够收到消息,如果由用户取消了关注,那么将不会再收到消息,也是满足我们的需求的。
由此看来使用Java内置的观察者模式也能够满足我们的日常需求,但是Java内置的观察者模式是有一些问题的,最大的问题就在于Observable是一个类而非接口,这样一来,如果我们想定义一个观察对象,就必须继承Observable类,因为Java是不支持多重继承的,所以如果我们还想让观察对象继承另一个超类,那是无法实现的,这样就限制了程序的可扩展性。因此我们还是建议自行实现观察者模式,尽量减少使用Java内置的观察者模式。
3、总结
通过上面案例的介绍,我们对观察者模式有了一定的了解,当我们需要在一个对象状态改变时通知它的依赖者,我们可以使用观察者模式。
观察者模式的有点在于能够使观察对象和观察者之间保持松耦合的状态,因为它们都是通过接口进行交互的。
但是观察者模式也存在一些缺点:首先,如果一个对象有很多的观察者,那么通知所有的观察者将会很耗费时间;另外,如果观察对象和观察者之间互为依赖的话,就很可能出现循环调用的情况,这样容易导致程序崩溃。
4、参考资料
[1] : http://www.runoob.com/design-pattern/observer-pattern.html “观察者模式”