设计模式(3):行为型-观察者模式(Observer)

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

设计模式分为三种类型,共23种。
创建型模式(5):单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
结构型模式(7):适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
行为型模式(11):(父子类)策略模式、模版方法模式,(两个类)观察者模式、迭代器模式、职责链模式、命令模式,(类的状态)状态模式、备忘录模式,(中间类) 访问者模式、中介者模式、解释器模式。

一.概述

定义

  观察者模式(Observer Pattern):定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。
  Observer Pattern:Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

结构

观察者模式结构中通常包括观察目标和观察者两个继承层次结构,其结构如图3所示:
这里写图片描述
在观察者模式结构图中包含如下几个角色:

  • Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。
  • ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。
  • Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。
  • ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的update()方法。通常在实现时,可以调用具体目标类的attach()方法将自己添加到目标类的集合中或通过detach()方法将自己从目标类的集合中删除。
      观察者模式描述了如何建立对象与对象之间的依赖关系,以及如何构造满足这种需求的系统。观察者模式包含观察目标和观察者两类对象,一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生改变,所有的观察者都将得到通知。作为对这个通知的响应,每个观察者都将监视观察目标的状态以使其状态与目标状态同步,这种交互也称为发布-订阅(Publish-Subscribe)。观察目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通知。

实现

下面通过示意代码来对该模式进行进一步分析。首先我们定义一个抽象目标Subject,典型代码如下所示:

public abstract class Subject {    
    //定义一个观察者集合用于存储所有观察者对象    
protected ArrayList observers<Observer> = new ArrayList();    

//注册方法,用于向观察者集合中增加一个观察者    
    public void attach(Observer observer) {    
    observers.add(observer);    
}    

    //注销方法,用于在观察者集合中删除一个观察者    
    public void detach(Observer observer) {    
    observers.remove(observer);    
}    

    //声明抽象通知方法    
    public abstract void notify();    
}    

具体目标类ConcreteSubject是实现了抽象目标类Subject的一个具体子类,其典型代码如下所示:

public class ConcreteSubject extends Subject {    
    //实现通知方法    
    public void notify() {    
        //遍历观察者集合,调用每一个观察者的响应方法    
        for(Object obs:observers) {    
            ((Observer)obs).update();    
        }    
    }       
}    

抽象观察者角色一般定义为一个接口,通常只声明一个update()方法,为不同观察者的更新(响应)行为定义相同的接口,这个方法在其子类中实现,不同的观察者具有不同的响应方法。抽象观察者Observer典型代码如下所示:

public interface Observer {    
    //声明响应方法    
    public void update();    
}    

在具体观察者ConcreteObserver中实现了update()方法,其典型代码如下所示:

public class ConcreteObserver implements Observer {    
    //实现响应方法    
    public void update() {    
        //具体响应代码    
    }    
}  

在有些更加复杂的情况下,具体观察者类ConcreteObserver的update()方法在执行时需要使用到具体目标类ConcreteSubject中的状态(属性),因此在ConcreteObserver与ConcreteSubject之间有时候还存在关联或依赖关系,在ConcreteObserver中定义一个ConcreteSubject实例,通过该实例获取存储在ConcreteSubject中的状态。如果ConcreteObserver的update()方法不需要使用到ConcreteSubject中的状态属性,则可以对观察者模式的标准结构进行简化,在具体观察者ConcreteObserver和具体目标ConcreteSubject之间无须维持对象引用。如果在具体层具有关联关系,系统的扩展性将受到一定的影响,增加新的具体目标类有时候需要修改原有观察者的代码,在一定程度上违反了“开闭原则”,但是如果原有观察者类无须关联新增的具体目标,则系统扩展性不受影响。

二.源码分析

JavaSE中已经提供了Observer接口和Observable类让你简单快速的实现观察者模式,因此有必要去了解Observer和Observable;

Observer接口

Observer为java.util包下的一个接口,源码如下:

public interface Observer {
    void update(Observable o, Object arg);
}

update方法第一个参数为被观察者对象,可以调用它的public方法。为观察者提供了一种拉取数据的方式
第二个参数由调用者调用notifyObservers(Object obj)将一些信息推过来。

Observable类

Observable类同样位于java.util包,该类的成员变量和方法(省略其具体实现)如下:

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    public Observable(){};
    protected synchronized void setChanged(){};
    protected synchronized void clearChanged(){};
    public synchronized void addObserver(Observer o){};
    public synchronized void deleteObserver(Observer o) {};
    public synchronized void deleteObservers(){};
    public synchronized boolean hasChanged(){};
    public synchronized int countObservers(){};
    public void notifyObservers(){};
    public void notifyObservers(Object arg){};
}

Vector相比于ArrayList来说,它是线程安全的。其次,在添加和删除观察者时对两个方法使用了synchronized关键字,这都是在为多线程考虑。
最关键的方法是notifyObservers(),来看下源码:

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

这个方法相当的严谨,考虑了线程安全等问题。
从这个方法可以看出,只有changed为true,才会执行各个观察者的update方法。对changed状态的改变的唯一途径是protected synchronized void setChanged(){}; 而protected修饰了setChanged方法,表明只有它的子类才能调用它(打开这个开关),所以客户端无论怎么调用notifyObservers()方法还是无济于事。
Observable类只提供这个boolean值来表明是否发生变化,而不定义什么叫变化,因为每个业务中对变化的具体定义不一样,因此子类自己来判断是否变化;

三.小丑表演

观察者模式是很常用的模式,尤其在界面编程,比如android中的BaseAdapter,就使用了观察者模式,当数据源发生变化时,通知界面重新绘制。下面我们来利用jdk中提供的Observer和Observable来实现一个观察者模式的例子,例子非常得有趣。
需求:
镇上来了一位小丑,为大家表演节目,所有观看的观众会根据小丑表演的精彩与否来做出相应的反应,比如表演的好就鼓掌喝彩,表演的不好就倒喝彩,表演完毕观众就退场。

分析:
这里涉及到小丑和观众,小丑的数量是1,而观众的数量是n,观众会对小丑的举动做出相应的反应。这里很符合观察者模式的场景,因此,我们需要创建一个小丑类Clown,作为被观察对象,因此需要继承Observable类,同时具有表演的行为,退场的行为;同时需要创建一个观众类Viewer,作为观察者,因此需要实现Observer接口,同时观众具有喝彩的行为,倒喝彩的行为,退场的行为,每个观众还对应一个座位号。

public class Clown extends Observable{
    public static final int PERFORM_GOOD = 0;
    public static final int PERFORM_BAD = 1;
    public static final int PERFORM_COMPLETE = 2;
    public void perfom() {
        if(countObservers()==0) {
            System.out.println("都没人,表演个毛线!!!!");
            return;
        }
        //此参数可以由客户端传递过来
        int random = new Random().nextInt(2);
        setChanged();
        notifyObservers(random);
    }
    public void exit() {
        System.out.println("表演结束,谢谢大家!");
        setChanged();
        notifyObservers(PERFORM_COMPLETE);
    }
}

public class Viewer implements Observer{
    private int seatNo;//座位号
    public Viewer(int seatNo) {
        this.seatNo = seatNo;
    }
    @Override
    public void update(Observable o, Object arg) {
        int r = (int)arg;
        switch (r) {
        case Clown.PERFORM_BAD:
            System.out.println(seatNo+"号观众:表演得太烂了吧!");
            if(new Random().nextBoolean()) {
                o.deleteObserver(this);
                System.out.println(seatNo+"号观众已经中途退场!!!!");
            }
            break;
        case Clown.PERFORM_GOOD:
            System.out.println(seatNo+"号观众:太好笑了,不错不错!");
            break;
        case Clown.PERFORM_COMPLETE:
            o.deleteObserver(this);
            System.out.println(seatNo+"号观众离场");
            break;
        }
    }

}
public class ObserverApp {

    public static void main(String[] args) {
        Clown c = new Clown();
        for(int i=0;i<10;i++) {
            Viewer v = new Viewer(i+1);
            c.addObserver(v);
        }
        System.out.println("观众都已经入座,小丑,请开始你的表演!");
        c.perfom();
        System.out.println("请在换个节目表演一次!");
        c.perfom();
        System.out.println("请在换个节目表演一次!");
        c.perfom();
        c.exit();
        c.perfom();
    }
}
观众都已经入座,小丑,请开始你的表演!
10号观众:太好笑了,不错不错!
9号观众:太好笑了,不错不错!
8号观众:太好笑了,不错不错!
7号观众:太好笑了,不错不错!
6号观众:太好笑了,不错不错!
5号观众:太好笑了,不错不错!
4号观众:太好笑了,不错不错!
3号观众:太好笑了,不错不错!
2号观众:太好笑了,不错不错!
1号观众:太好笑了,不错不错!
请在换个节目表演一次!
10号观众:表演得太烂了吧!
10号观众已经中途退场!!!!
9号观众:表演得太烂了吧!
9号观众已经中途退场!!!!
8号观众:表演得太烂了吧!
8号观众已经中途退场!!!!
7号观众:表演得太烂了吧!
6号观众:表演得太烂了吧!
5号观众:表演得太烂了吧!
5号观众已经中途退场!!!!
4号观众:表演得太烂了吧!
3号观众:表演得太烂了吧!
2号观众:表演得太烂了吧!
1号观众:表演得太烂了吧!
1号观众已经中途退场!!!!
请在换个节目表演一次!
7号观众:表演得太烂了吧!
6号观众:表演得太烂了吧!
6号观众已经中途退场!!!!
4号观众:表演得太烂了吧!
4号观众已经中途退场!!!!
3号观众:表演得太烂了吧!
2号观众:表演得太烂了吧!
表演结束,谢谢大家!
7号观众离场
3号观众离场
2号观众离场
都没人,表演个毛线!!!!

可以参考博客:【java】Observer和Observable详解
参考电子书下载:设计模式的艺术–软件开发人员内功修炼之道_刘伟(2013年).pdf

《道德经》第六章:
谷神不死,是谓玄牝。玄牝(pin)之门,是谓天地之根。绵绵呵!其若存!用之不堇(jin)。
译文:生养天地万物的道(谷神)是永恒长存的,这叫做玄妙的母性。玄妙母体的生育之产门,这就是天地的根本。连绵不绝啊!它就是这样不断的永存,作用是无穷无尽的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值