《大设》第十七篇之观察者模式

来源:互联网 时间:2016-11-08

前言

国际惯例,本文仍然是在学习设计模式的路上所写,希望对同样在学习设计模式的童靴有点作用,大牛误入的话还请给点宝贵意见,感激不尽。

模式解析

上一篇我们学习了适配器模式,今天我们继续来看观察者模式,按照大话设计模式中的顺序应该是先观察者模式再适配器模式,但是在看观察者模式的时候发现需要适配器模式来擦屁股,没办法只好先去看一下适配器模式了。

那么什么是观察者模式呢?我们先看一下百度百科给它的定义:

观察者模式(有时又称为发布-订阅模式、模型-视图模式、源-收听者模式或者从属者模式)是软件设计模式的一种。在此模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事物处理系统。

通过上面的定义我们可以总结出来几个关键词:被观察者、管理、状态改变、观察者、通知,总结起来就是被观察者管理着观察者,在被观察者状态改变时通知观察者,这句话其实就是所谓的观察者模式。

接下来我们先看一下观察者模式的类图,还是采用百度百科的UML类图

在观察者模式中主要有以下两个角色:

被观察者:类图中的Subject是抽象的被观察者,ConcreteSubject是具体的被观察者;

观察者:类图中的Observer是抽象的观察者,ConcreteObserver是具体的观察者;

被观察者中持有观察者对象的集合,也就是上面所说的被观察者管理观察者,并且在被观察者中拥有添加和删除观察者的方法,并且拥有通知观察者的方法,在观察者中拥有被观察者状态发生改变时观察者做出响应的方法。

下面是简单的代码实现:

首先需要一个抽象的观察者接口,接口中只有一个update方法:

public interface Observer {

public void update(Subject subject);

}

以下是两个具体的观察者类,两个类类似,只贴出一个代码:

public class ConcreteObserver1 implements Observer{

@Override

public void update(Subject subject) {

System.out.println("ConcreteObserver1接收到被观察者的状态改变通知,获取被观察者状态是:"+subject.getStates());

}

}

然后是被观察者类,这里我就不再写被观察者接口了,只写一个被观察者类:

public class Subject {

private boolean changed = false;

private List<Observer> list = new ArrayList<>();

/*添加被观察者*/

public void attach(Observer observer){

if(observer == null){

throw new RuntimeException("observer null");

}

if(!list.contains(observer)){

list.add(observer);

}

}

/*删除被观察者*/

public void dettach(Observer observer){

list.remove(observer);

}

/*被观察者状态改变*/

public void changed(){

System.out.println("被观察者状态改变了,通知观察者");

notifyObservers();

}

/*通知观察者*/

private void notifyObservers(){

for(Observer observer:list){

observer.update(this);

}

}

/*获取到被观察者状态*/

public boolean getStates(){

return this.changed;

}

/*设置被观察者状态*/

public void setStates(boolean states){

this.changed = states;

}

}

测试类如下所示:

public static void main(String[] args) {

Subject subject = new Subject();

Observer observer1 = new ConcreteObserver1();

Observer observer2 = new ConcreteObserver2();

subject.attach(observer1);

subject.attach(observer2);

subject.setStates(true);

subject.changed();

System.out.println("-----------------------");

subject.dettach(observer2);

subject.setStates(false);

subject.changed();

}

上面代码的运行结果:

上面的代码简单的实现了一下观察者模式,但是偷懒只写了一个被观察者类,大家不要见怪,其实上面的代码可以做一下变形,怎么变形呢?这个需要大家首先想一个问题,在学校的时候,是每个学生记住老师的名字容易还是老师记住每个学生的名字容易,很显然是后者,因此我们可以对上面的代码做一个变形,至于变形的代码怎么写我们先不说。

我们先看一下JAVA对于观察者模式的支持,在JAVA的util包中有这么一个接口和一个类,具体的代码如下(我在左潇龙的博客中找到了注释过的源代码,所以就直接粘贴复制了,原文地址)

首先是观察者接口:

//观察者接口,每一个观察者都必须实现这个接口

public interface Observer {

//这个方法是观察者在观察对象产生变化时所做的响应动作,从中传入了观察的对象和一个预留参数

void update(Observable o, Object arg);

}

然后是被观察者类:

import java.util.Vector;

//被观察者类

public class Observable {

//这是一个改变标识,来标记该被观察者有没有改变

private boolean changed = false;

//持有一个观察者列表

private Vector 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);

}

//notifyObservers(Object arg)的重载方法

public void notifyObservers() {

notifyObservers(null);

}

//通知所有观察者,被观察者改变了,你可以执行你的update方法了。

public void notifyObservers(Object arg) {

//一个临时的数组,用于并发访问被观察者时,留住观察者列表的当前状态,这种处理方式其实也算是一种设计模式,即备忘录模式。

Object[] arrLocal;

//注意这个同步块,它表示在获取观察者列表时,该对象是被锁定的

//也就是说,在我获取到观察者列表之前,不允许其他线程改变观察者列表

synchronized (this) {

//如果没变化直接返回

if (!changed)

return;

//这里将当前的观察者列表放入临时数组

arrLocal = obs.toArray();

//将改变标识重新置回未改变

clearChanged();

}

//注意这个for循环没有在同步块,此时已经释放了被观察者的锁,其他线程可以改变观察者列表

//但是这并不影响我们当前进行的操作,因为我们已经将观察者列表复制到临时数组

//在通知时我们只通知数组中的观察者,当前删除和添加观察者,都不会影响我们通知的对象

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();

}

}

有没有发现上面的代码和前面的例子很像,其实就是差不多的,但是类库源代码肯定是比我写的逼格高了很多,具体的分析上面的注释中也有,感谢左潇龙大哥的无私分享。

这个咱先不说,继续说前面提到的修改代码的问题,首先我们根据上面的一个接口和一个类对我们前面的例子进行改造,让被观察者继承Obserable类,让观察者实现Observer接口。

我们举个例子来说可能会好理解一点,就拿猎头和找工作的人来说吧,猎头有新工作了就通知给应聘者,不管有多少应聘者,只需要通知就行了,下面是猎头的代码:

public class Hunter extends Observable{

private String name;

private String newWork;

public Hunter(String name){

this.name = name;

}

public String getName() {

return name;

}

public String getNewWork() {

return newWork;

}

public void publishNewWork(String newWork){

System.out.println(this.name +"发布了新工作"+newWork);

this.newWork = newWork;

super.setChanged();

super.notifyObservers();

}

}

然后是应聘者:

public class Worker implements Observer{

private String name;

public Worker(String name){

this.name = name;

}

public String getName() {

return name;

}

public void attach(Observable o){

o.addObserver(this);

}

public void dettach(Observable o){

o.deleteObserver(this);

}

@Override

public void update(Observable o, Object arg) {

if(o instanceof Hunter){

Hunter hunter = (Hunter)o;

System.out.println(this.name +"接到猎头"+hunter.getName()+"的通知,知道了新工作"+hunter.getNewWork());

}

}

}

测试代码:

public static void main(String[] args) {

Hunter hunter1 = new Hunter("张三");

Hunter hunter2 = new Hunter("李四");

Worker worker1 = new Worker("小李");

Worker worker2= new Worker("小王");

worker1.attach(hunter1);

worker2.attach(hunter1);

worker2.attach(hunter2);

hunter1.publishNewWork("JAVA开发");

hunter2.publishNewWork("PHP开发");

}

测试结果:

对比修改前后的代码大家可以发现,修改后的代码中我把添加观察者和删除观察者的方法放到了观察者中,为什么这么做呢?还是上面说的学生记住老师比老师记住所有的学生要容易的多,这就相当于被观察者将维护通知对象的职能转化给了观察者,毕竟观察者可以是多个,全部交给被观察者去管理是很困难的。

前面说了那么多,那到底什么情况下才使用观察者模式呢?其实观察者模式是为了解决一个问题,什么问题呢?就是在将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维护一致性而使得各类紧密耦合,这样会给维护、扩展和重用带来不便(引自大设)。

上面这段话的意思就是说当一个类的改变会引起一系列的类的改变时,如果在各个类中维护一致性,那么会造成很强的耦合度,不利于系统的扩展和维护,这时可以选择使用观察者模式(当然也可以使用事件驱动模型)。

当然,文章开头时候我说到了要适配器模式给观察者模式擦屁股,这是什么鬼呢?前面的例子大家也看到了,观察者是需要实现Observer接口的,但是如果这个要观察的类已经存在或者说我们无法修改其源代码,这时候怎么搞?这时候就用到了我们所说的适配器模式,将存在的类中的方法适配为我们需要的方法即可,当然这也暴露出观察者模式的一个缺点,就是说如果有很多已经存在的类需要作为观察者,那么就需要给每个类都添加一个适配器,这个就有点尴尬了。但是事情都有两面性嘛,还是得灵活使用,不能说因噎废食吧。

OK,今天的观察者模式就到这里了,我们下期再见。

参考文章

  1. http://www.cnblogs.com/zuoxiaolong/p/pattern7.html

相关阅读:
Top