设计模式

设计模式之观察者模式

2018-04-09  本文已影响25人  Ludwigvan

设计模式之观察者模式

本篇是设计模式系列博客的第四篇,本篇主要学习设计模式中的第二个行为型模式---观察者模式。

  1. 什么是观察者模式?
  2. 模式的结构?
  3. 模式自定义实现和JDK自带实现?
  4. 推模型和拉模型?
  5. JDK源码分析?
  6. 总结!

1 什么是观察者模式?

引用百度百科中的解释:

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

简单点概括成通俗的话来说,就是一个类管理着所有依赖于它的观察者类,并且在它状态变化时会主动给这些依赖它的类发出通知。当然后面我们也可以看到除了主动推消息外也可以拉消息。

2 模式的结构

把它用图表示如下:

image

可以看到,我们的被观察者类Observable只关联了一个Observer的列表,然后在自己状态变化时,使用notifyObservers方法通知这些Observer,具体这些Observer都是什么,被观察者是不关心也不需要知道的。

上面就将观察者和被观察者二者的耦合度降到很低了,而我们具体的观察者是必须要知道自己观察的是谁,所以它依赖于被观察者。

3 模式实现

3.1 自定义观察者模式

下面LZ给写出一个很简单的观察者模式-拉模型,来使用JAVA代码简单诠释一下上面的类图。

//这个接口是为了提供一个统一的观察者做出相应行为的方法
public interface Observer {

    void update(Observable o);
    
}

具体的观察者1:

public class ConcreteObserver1 implements Observer{

    public void update(Observable o) {
        System.out.println("观察者1观察到" + o.getClass().getSimpleName() + "发生变化");
        System.out.println("观察者1执行pull拉取动作");
    }

}

具体的观察者2:

public class ConcreteObserver2 implements Observer{

    public void update(Observable o) {
        System.out.println("观察者2观察到" + o.getClass().getSimpleName() + "发生变化");
        System.out.println("观察者2执行pull拉取动作");
    }

}

下面是被观察者,它有一个观察者的列表,并且有一个通知所有观察者的方法,通知的方式就是调用观察者通用的接口行为update方法。下面我们看它的代码。

public class Observable {

List<Observer> observers = new ArrayList<Observer>();

public void addObserver(Observer o){
    observers.add(o);
}

public void changed(){
    System.out.println("我是被观察者,我已经发生变化了");
    notifyObservers();//通知观察自己的所有观察者
}

public void notifyObservers(){
    for (Observer observer : observers) {
        observer.update(this);
    }
}

}

这里面很简单,新增两个方法,一个是为了改变自己的同时通知观察者们,一个是为了给客户端一个添加观察者的公共接口。
下面我们使用客户端调用一下,看一下客户端如何操作。

public class Client {

    public static void main(String[] args) throws Exception {
        Observable observable = new Observable();
        observable.addObserver(new ConcreteObserver1());
        observable.addObserver(new ConcreteObserver2());
        
        observable.changed();
    }
}

输出结果:

   我是观察者,我已经发生了变化
   观察者1观察到Obserable发生了变化
   观察者1执行动作
   观察者2观察到Obserable发生了变化
   观察者2执行动作

3.2 JDK自带的观察者模式

JDK提供了一个Observable(被观察者)抽象类,和一个Observer(观察者)接口。继承前者就可以被当做一个Subject主题,实现后面接口就可以成为Subject的一个观察者。主题需要显示调用setChanged来告诉Observable,我们状态改变需要通知给观察者,也提供了update的重载方法,可以实现推拉两种模式。观察者只需要实现update方法就可以获取被观察者的状态。

4 推模型和拉模型?

4.1 推模型

主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。

//这个接口是为了提供一个统一的观察者做出相应行为的方法
public interface Observer {

    void update();
    
}

具体的观察者:

public class ConcreteObserver2 implements Observer{

    private Observable observable;
    
    public void setObservable(Observable observable){
        this.observable =observable;
    }

    public Observable getObservable(){
        return observable;
    }
    public void update() {
        
        System.out.println("观察者观察到" + observable.getState + "发生变化");
    
    }

}

下面是被观察者,它有一个观察者的列表,并且有一个通知所有观察者的方法,通知的方式就是调用观察者通用的接口行为update方法。下面我们看它的代码。

public class Observable {

private String state;
public void setState(String state){
    system.out.pritln("被观察者改变了状态"+state);
    this.state =state;
}

public String getState(){
    return state;
}


List<Observer> observers = new ArrayList<Observer>();

public void addObserver(Observer o){
    observers.add(o);
}


public void notifyObservers(){
    for (Observer observer : observers) {
        observer.update();
    }
}

}

这里面很简单,新增两个方法,一个是为了改变自己的同时通知观察者们,一个是为了给客户端一个添加观察者的公共接口。
下面我们使用客户端调用一下,看一下客户端如何操作。

public class Client {

    public static void main(String[] args) throws Exception {
        Observable observable = new Observable();
        observable.addObserver(new ConcreteObserver1());
        observable.addObserver(new ConcreteObserver2());
        
        
        //被观察者改变状态
        observable.setState("change state");
        
        //被观察者推送
        observable.update();
    }
}

输出结果:

   被观察者改变了状态change state
  
   观察者观察到change state发生了变化
   

4.2 拉模型

主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

这种模式在文章的前面已经给出栗子了。

5. JDK源码分析

其实源码思想跟我们前面自己实现的差别不大只是做了一个封装。先看主题抽象类。代码如下(代码不多我全部贴上,写上必要的注释):

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

现在是观察者接口:

public interface Observer {
    //就这么一个方法
    void update(Observable o, Object arg);
}

6 总结!

观察者模式所欠缺的是设计上的问题,即观察者和被观察者是多对一的关系,那么反过来的话,就无法支持了。

观察者模式还有一个缺点就是,每一个观察者都要实现观察者接口,才能添加到被观察者的列表当中,假设一个观察者已经存在,而且我们无法改变其代码,那么就无法让它成为一个观察者了,不过这个我们依然可以使用适配器模式解决。但是还有一个问题就不好解决了,就是假如我们很多类都是现成的,当被观察者发生变化时,每一个观察者都需要调用不同的方法,那么观察者模式就有点捉襟见肘的感觉了,我们必须适配每一个类去统一他们变化的方法名称为update,这是一个很可怕的事情。

上一篇下一篇

猜你喜欢

热点阅读