Java设计模式之观察者模式
简介
在上一篇文章中介绍了设计模式的概念和最简单的设计模式-单例模式,单例模式之所以简单,是因为我们使用的时候只需要记住一点,那就是”独一无二的对象”就可以。今天我们要介绍的观察者模式相比单例模式肯定要复杂一些。但是只要你认真看完我写的这些东西,掌握观察者模式对你来说就不是什么难事,现在就让我们开始吧!
1.观察者模式
首先你需要了解什么是观察者模式。在介绍观察者模式之前,我先给你举个例子,看了这个例子之后,你就明白了。比如现在一款知名的杂志,上面有很多精彩的内容,为了不错过每一期,你可能会去和杂志社的主编说”hey,主编,我要订阅这份杂志”。当然别的人也可以订阅这份杂志。所以结果就是,每当出了新的杂志,所有订阅了这份杂志的人都会第一时间收到这份杂志。如果有一天你不想继续订阅了,你去取消订阅,当以后再有更新也就不会再通知你了。这就是观察者模式的一种典型例子。
观察者模式是一种基于订阅-通知类型的设计模式。也就是说如果我们对某件事情感兴趣(例如杂志),那么我们必须先去订阅它,然后当有更新时候会通知每一个订阅了该事件的对象。观察者模式定义了对象之间一种一对多的依赖关系,当一个对象的状态更新时,它的依赖者就会收到这种改变并自动更新。我们通常将被订阅的对象叫做主题对象,订阅的对象叫做订阅对象。可以用如下的图来帮助理解:
所以 观察者模式=订阅者+主题对象。
2.代码实现
为了帮助理解,我们来考虑这样一种场景。比如说市中心新建了一套房子,很多人都对这套房子感兴趣,但是觉得现在价格较贵,所以他们会说”当这套房子降价的时候务必第一时间告诉我”。通过这种场景,来说明具体怎么用代码实现观察者模式。
通过上面这个例子,所以我们想到,可能需要如下的几个类。买家A,买家B,房子类。买家A和买家B都需要能够订阅房子,所以他们可能需要实现相同的接口。房子需要有通知订阅者的功能,所以也可能需要实现某个接口。所以在观察者模式中,订阅者需要实现同一个订阅接口(我们叫做Observer接口),被订阅者需要实现被订阅接口(我们叫做Observable接口)。既然清楚了需要哪些类,那么我们就开始代码来实现。
2.1订阅者接口
思考一下订阅者接口需要哪些功能。当我们订阅了某个事件,如果事件有更新,所有订阅者就会收到这个更新来更新自己的状态。Observer接口定义如下:
public interface Observer {
void update(Object object);
}
其中update方法接受object参数,表示更新时接收的数据,比如房价。
2.2被订阅者接口
思考一下被订阅者需要哪些功能。首先需要能够添加订阅者,删除订阅者,其次还需要能够通知更新。所以将Observable接口定义如下:
public interface Observable {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyUpdate(Object object);
}
2.3买家类
买家类需要实现Observer接口。实现类如下:
public class Customer implements Observer {
private String name;
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void update(Object object) {
if (object instanceof Float) {
System.out.println(getName() + " 收到了价格更新,更新后的价格是" + object);
}
}
}
2.4房子类
房子类需要实现Observable接口。实现如下:
import java.util.ArrayList;
import java.util.List;
public class House implements Observable {
private double price;
private List<Observer> observers;
public House() {
observers = new ArrayList<>();
setPrice(1000000.00);//初始价格100万
}
public void setPrice(double price) {
System.out.println("House setPrice " + price);
this.price = price;
notifyUpdate(price);
}
public double getPrice() {
return price;
}
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyUpdate(Object object) {
if (object instanceof Double) {
for (Observer observer : observers) {
observer.update(object);
}
}
}
}
在House类中,我们使用ArrayList容器类存储所有的Observer对象。House有一个price属性,当调用setPrice方法的时候,我们就认为房子的价格发生了变化,此时就需要去通知所有的订阅者更新。
2.5测试类
测试类定义如下:
public class ObserverTest {
public static void main(String[] args) {
//新建两个买家对象
Customer customerA = new Customer("CustomerA");
Customer customerB = new Customer("CustomerB");
Customer customerC = new Customer("CustomerC");
//新建一个房子对象
House house = new House();
//买家订阅房子对象
house.addObserver(customerA);
house.addObserver(customerB);
house.addObserver(customerC);
//房子降价到90万
house.setPrice(900000);
}
}
此时运行代码,得到如下的结果:
House setPrice 1000000.0
House setPrice 900000.0
CustomerA 收到了价格更新,更新后的价格是900000.0
CustomerB 收到了价格更新,更新后的价格是900000.0
CustomerC 收到了价格更新,更新后的价格是900000.0
所以可以看到A,B,C确实是收到了价格的更新。可能你会问,如果我有一天我不想买了,我不再需要更新信息了怎么办?很简单,你只需要取消订阅就可以。比如,买家B不再订阅。如下:
package test;
public class ObserverTest {
public static void main(String[] args) {
//新建两个买家对象
Customer customerA = new Customer("CustomerA");
Customer customerB = new Customer("CustomerB");
Customer customerC = new Customer("CustomerC");
//新建一个房子对象
House house = new House();
//买家订阅房子对象
house.addObserver(customerA);
house.addObserver(customerB);
house.addObserver(customerC);
//房子降价到90万
house.setPrice(900000);
//买家B取消订阅
house.removeObserver(customerB);
//房子降价到80万
house.setPrice(800000);
}
}
运行代码得到如下的结果:
House setPrice 1000000.0
House setPrice 900000.0
CustomerA 收到了价格更新,更新后的价格是900000.0
CustomerB 收到了价格更新,更新后的价格是900000.0
CustomerC 收到了价格更新,更新后的价格是900000.0
House setPrice 800000.0
CustomerA 收到了价格更新,更新后的价格是800000.0
CustomerC 收到了价格更新,更新后的价格是800000.0
可以很清楚的看到,第一次价格更新,三个买家都收到了更新,但是B取消了订阅之后,B就再也不会受到更新的消息,如果有一天又想收到,那么重新订阅就可以,我们的House可是个好脾气!不管你曾经是不是曾经抛弃过它,只要你再次需要它它就会接受你。
3.Java内置对观察者模式的支持
可能有些人会想,你这也太麻烦了,这接口那接口的太多了,太难以记住,不用怕,java本身内置了对观察者模式的支持。在java的util包下有两个接口Observer和Observable两个接口。从这两个的取名上来看,和我们自己写的差不多。那么来看一下源码吧。
Observer.java
package java.util;
/**
* A class can implement the <code>Observer</code> interface when it
* wants to be informed of changes in observable objects.
*
* @author Chris Warth
* @see java.util.Observable
* @since JDK1.0
*/
public interface Observer {
void update(Observable o, Object arg);
}
可以看出和我们的定义几乎差不多。
再来看看Observable。
Observable.java
package java.util;
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return obs.size();
}
}
首先来看看与我们自己定义的Observable有什么不同。
1. java内置的Observable是一个类,所以我们需要的是继承它。
2. java内置的使用Vector来存储Observer。
3. 增加了同步操作。
其它的其实和我们自己定义的差不多,只要你看了前面自定义的实现的过程,看懂这个其实并不难。
那么现在就用java内置观察者模式来实现前面的实例吧。
3.1买家类
import java.util.Observable;
import java.util.Observer;
public class Customer implements Observer {
private String name;
public Customer(String name) {
setName(name);
}
@Override
public void update(Observable o, Object arg) {
if (arg instanceof Double) {
System.out.println(getName() + " 收到了价格更新,更新后的价格是 " + arg);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.2房子类
import java.util.Observable;
public class House extends Observable {
private double price;
public House() {
setPrice(1000000);
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
System.out.println("House setPrice " + price);
this.price = price;
setChanged();//设置更新状态
notifyObservers(price);
}
}
3.3测试类
public class ObserverTest {
public static void main(String[] args) {
//定义三个买家
Customer customerA = new Customer("CustomerA");
Customer customerB = new Customer("CustomerB");
Customer customerC = new Customer("CustomerC");
//定义一个房子
House house = new House();
//添加订阅
house.addObserver(customerA);
house.addObserver(customerB);
house.addObserver(customerC);
//更新价格
house.setPrice(900000);
//B取消订阅
house.deleteObserver(customerB);
//更新价格
house.setPrice(800000);
}
}
运行结果如下:
House setPrice 1000000.0
House setPrice 900000.0
CustomerC 收到了价格更新,更新后的价格是 900000.0
CustomerB 收到了价格更新,更新后的价格是 900000.0
CustomerA 收到了价格更新,更新后的价格是 900000.0
House setPrice 800000.0
CustomerC 收到了价格更新,更新后的价格是 800000.0
CustomerA 收到了价格更新,更新后的价格是 800000.0
可以看到的是效果和自定义的效果是一样的。但是在使用系统内置的观察者模式的时候,更新(调用notifyObservers之前一定要先调用setChanged方法设置状态为更新状态,否则无法实现更新).
4.到底该用哪种方式
前面介绍了两种实现观察者模式的方式,一种是自起炉灶,一种是用java内置的观察者模式。到底哪种好。
首先,java.util.Observable是一个类!是的,它是一个类,而且更麻烦的是它没有实现任何的接口,这就从某种程度上限制了它的使用。java.util.Observable的实现有一些问题,但是这并不意味这它没有提供相应的功能,只是说它没有想象中的那么有用。因为它是一个类。所以说如果你能够拓展Observable类,那么这个类就是有用的,相反,你可能需要像我们前面那样自己来实现一个观察者模式,反正这也不是一个困难的过程。反正你已经掌握了观察者模式,你就应该善用它们。
第二从代码的角度上来说,自己实现一个观察者模式也并不难,而且通过接口的方式来设计更符合java面向抽象编程的思想。
所以从如上的角度分析,如果你可以在代码中拓展java.util.Observalbe类,那么就可以使用java内置观察者;相反如果你无法拓展这个类,那么就应该自己来实现一个观察者模式。
5.总结
哇,花了这么长的篇幅,终于讲清了观察者这个非常棒的设计模式,因为有了观察者,你就不会错过任何你感兴趣的事情(前提是你订阅了它)。有了观察者,你就会消息灵通。
好了,到了这里,这篇文章就要结束了。下一篇文章,咱们要讲解的是Java设计模式之装饰器模式。