Java 观察者模式

Java 观察者模式

Posted by 金志宏 on April 12, 2019

Java 观察者模式

1. 什么是观察者模式

观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

一个软件系统里面包含了各种对象,就像一片欣欣向荣的森林充满了各种生物一样。在一片森林中,各种生物彼此依赖和约束,形成一个个生物链。一种生物的状态变化会造成其他一些生物的相应行动,每一个生物都处于别的生物的互动之中。

  同样,一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其他的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作。观察者模式是满足这一要求的各种设计方案中最重要的一种。

eoCGDI.png

2. 一个实例

  • 抽象主题类 Subject
/**
 * 主题抽象类
 * @author jinzhihong
 */
public abstract class Subject {

    /**
     * 保存注册对象
     */
    private List<Observer> observerList = new ArrayList();

    /**
     * 注册观察者对象方法
     * @param observer
     */
    public void attach(Observer observer){
        observerList.add(observer);
        System.out.println(" 注册了一个观察者 ");
    }

    /**
     * 删除观察者对象
     * @param observer
     */
    public void detach(Observer observer){
        observerList.remove(observer);
        System.out.println(" 删除了一个观察者 ");
    }

    /**
     * 通知所有已经注册的观察者更新状态
     * @param newState
     */
    public void notifyObservers(String newState){
        for (Observer observer: observerList){
            observer.update(newState);
        }
    }

}
  • 具体主题类 ConcreteSubject
/**
 * 具体主题类,继承抽象主题,定义change 状态方法
 * @author jinzhihong
 */
public class ConcreteSubject extends Subject {


    private String state;

    public String getState() {
        return state;
    }

    /**
     * 定义改变新状态方法并通知已注册的观察者
     * @param newState
     */
    public void change(String newState) {
        state = newState;
        System.out.println(" 通知观察者新状态: " + state);
        // 状态发生改变,通知观察者
        this.notifyObservers(state);
    }

}
  • 观察者接口 Observer
/**
 * 观察者接口
 * @author jinzhihong
 */
public interface Observer {

    /**
     * 定义状态更新接口
     * @param state
     */
    void update(String state);
}
  • 具体观察者实现接口ConcreteObserver
/**
 * 定义具体观察者实现观察者接口
 *
 * @author jinzhihong
 */
public class ConcreteObserver implements Observer {

    // 状态
    private String state;

    /**
     * 更新状态
     *
     * @param state
     */
    @Override
    public void update(String state) {
        this.state = state;
        System.out.println(" 更新观察者状态: " + state);
    }
}
  • Client执行
/**
 * @author jinzhihong
 */
public class Client {
    public static void main(String[] args) {

        // 创建主题
        ConcreteSubject subject = new ConcreteSubject();

        // 创建观察者对象
        Observer observer = new ConcreteObserver();

        // 注册观察者对象
        subject.attach(observer);

        // 改变主题对象状态
        subject.change(" 新主题 ");
    }
}

// 执行结果:
 注册了一个观察者 
 通知观察者新状态:  新主题 
 更新观察者状态:  新主题 

在运行时,这个客户端首先创建了具体主题类的实例,以及一个观察者对象。然后,它调用主题对象的attach()方法,将这个观察者对象向主题对象登记,也就是将它加入到主题对象的集合中去。这时,客户端调用主题的change()方法,改变了主题对象的内部状态。主题对象在状态发生变化时,调用超类的notifyObservers()方法,通知所有登记过的观察者对象。

3. 推模型和拉模型

在观察者模式中,又分为推模型和拉模型两种方式。

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

根据上面的描述,发现前面的例子就是典型的推模型,下面给出一个拉模型的实例。拉模型通常把主题对象作为参数传递给观察者。定义一个新的拉模型观察者 update(Subject subject) 接口。

  • 拉模型观察者接口 Observer
/**
 * 观察者接口
 *
 * @author jinzhihong
 */
public interface Observer {

    /**
     * 定义状态更新接口
     *
     * @param state
     */
    void update(String state);

    /**
     * 定义拉模型更新接口
     * @param subject
     */
    void update(Subject subject);
}
  • 拉模型观察者实例 ConcreteObserver
/**
 * 定义具体观察者实现观察者接口
 *
 * @author jinzhihong
 */
public class ConcreteObserver implements Observer {

    // 状态
    private String state;

    /**
     * 更新状态
     *
     * @param state
     */
    @Override
    public void update(String state) {
        this.state = state;
        System.out.println(" 更新观察者状态: " + state);
    }

    /**
     * 拉模型更新状态
     *
     * @param subject
     */
    @Override
    public void update(Subject subject) {
        state = ((ConcreteSubject) subject).getState();
        System.out.println(" 更新观察者状态: " + state);
    }
}
  • 主题抽象类Subject
/**
 * 主题抽象类
 *
 * @author jinzhihong
 */
public abstract class Subject {

    /**
     * 保存注册对象
     */
    private List<Observer> observerList = new ArrayList();

    /**
     * 注册观察者对象方法
     *
     * @param observer
     */
    public void attach(Observer observer) {
        observerList.add(observer);
        System.out.println(" 注册了一个观察者 ");
    }

    /**
     * 删除观察者对象
     *
     * @param observer
     */
    public void detach(Observer observer) {
        observerList.remove(observer);
        System.out.println(" 删除了一个观察者 ");
    }

    /**
     * 通知所有已经注册的观察者更新状态
     *
     * @param newState
     */
    public void notifyObservers(String newState) {
        for (Observer observer : observerList) {
            observer.update(newState);
        }
    }

    /**
     * 通知所有已经注册的观察者更新状态,并以自身实例作为参数
     */
    public void notifyObservers() {
        for (Observer observer : observerList) {
            observer.update(this);
        }
    }

  • 具体主题类 ConcreteSubject
/**
 * 具体主题类,继承抽象主题,定义change 状态方法
 *
 * @author jinzhihong
 */
public class ConcreteSubject extends Subject {

    private String state;
    

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    /**
     * 定义改变新状态方法并通知已注册的观察者
     *
     * @param newState
     */
    public void change(String newState) {
        state = newState;
        System.out.println(" 通知观察者新状态: " + state);
        // 状态发生改变,通知观察者
        this.notifyObservers(state);
    }

    /**
     * 定义拉模型改变新状态方法并通知已注册的观察者 把自己作为参数传递
     */
    public void change() {
        System.out.println(" 通知观察者新状态: " + state);
        // 状态发生改变,通知观察者
        this.notifyObservers();
    }

}
  • Clinet
/**
 * @author jinzhihong
 */
public class Client {
    public static void main(String[] args) {

        // 创建主题
        ConcreteSubject subject = new ConcreteSubject();

        // 创建观察者对象
        Observer observer = new ConcreteObserver();

        // 注册观察者对象
        subject.attach(observer);

        // 更改主题状态
        subject.setState(" Hello World ");

        // 更新并通知观察者
        subject.change();
    }
}

// 输出:
 注册了一个观察者 
 通知观察者新状态:  Hello World 
 更新观察者状态:  Hello World 

推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。

推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。

4. Java 提供的对观察者模式的支持

Javajava.util 类库,定义了Observable接口和Observer接口

4.1 Observer 接口

Observer 这个接口只定义了一个方法,即update()方法,当被观察者对象的状态发生变化时,被观察者对象的notifyObservers()方法就会调用这一方法。

package java.util;

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

4.2 Observable 接口

被观察者都是 Observable 的子类,它提供公开的方法支持观察者对象,这些方法中有两个对Observable的子类非常重要,一个是setChanged(),另一个是notifyObservers()。第一方法setChanged()被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。第二个notifyObservers()这个方法被调用时,会调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。

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;
			 	// 同步 
      	// 1. 是否发生变更 否 return
      	// 2. 初始化观察者数组
       	// 3. 重置是否变更标志位
        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();
    }

    // 同步方法 设置变更状态位 为true
    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();
    }
}

这个类表示被观察者对象,有时候称之为主题对象,一个被观察者可以有多个观察者对象,每个观察者对象实现Observer 接口的对象,在被观察者发生变化的时候,会调用 ObservablenotifyObservers() 方法,此方法调用所有具体观察者的 update方法,从而使得所有观察者都被通知执行更新方法。

4.3 怎么样使用 Java 对观察者模式进行支持

通过一个现实生活中的简单的实例来演示怎么通过 Java 对观察者模式进行支持。行人和汽车过十字路口都会观察红绿灯,以行人为主体如果是红灯则行人停汽车可以走,绿灯行人走汽车停。

  • 被观察者交通信号灯(红绿灯)TrafficLight
public class TrafficLight extends Observable {

    // 红绿灯
    private String light;


    public String getLight() {
        return light;
    }

    public void setLight(String light) {
        if(!light.equals(this.light)) {
            this.light = light;
            System.out.println("交通信号灯变为:" + light);
            setChanged();
        }
        notifyObservers();
    }
}
  • 观察者行人 Pedestrian
public class Pedestrian implements Observer {

    private String name;

    public Pedestrian(String name) {
        this.name = name;
    }


    // 定义update方法,行人红灯停,绿灯过马路
    @Override
    public void update(Observable o, Object arg) {
        String light =  ((TrafficLight)o).getLight();
        if("红灯".equals(light)) {
            System.out.println(name + " 停 ");
        }else if("绿灯".equals(light)) {
            System.out.println(name + " 过马路 ");
        }
    }
}
  • 观察者汽车 Vehicle
public class Vehicle implements Observer {

    public Vehicle(String name) {
        this.name = name;
    }

    private String name;


    // 定义update方法 汽车红灯可以过马路,绿灯停
    @Override
    public void update(Observable o, Object arg) {
        String light = ((TrafficLight) o).getLight();
        if ("红灯".equals(light)) {
            System.out.println(name + " 过马路 ");
        } else if ("绿灯".equals(light)) {
            System.out.println(name + " 停 ");
        }
    }
}
  • Client
public class Client {
    public static void main(String[] args) {

        TrafficLight trafficLight = new TrafficLight();
        trafficLight.addObserver(new Pedestrian("张三"));
        trafficLight.addObserver(new Pedestrian("李四"));
        trafficLight.addObserver(new Vehicle("轿车"));
        trafficLight.addObserver(new Vehicle("卡车"));

        trafficLight.setLight("红灯");
        trafficLight.setLight("绿灯");
    }
}

//输出
交通信号灯变为:红灯
卡车 过马路 
轿车 过马路 
李四  
张三  
交通信号灯变为:绿灯
卡车  
轿车  
李四 过马路 
张三 过马路 

以上实例很好理解,行人和车子都会观察红绿灯过马路。假设绿灯为行人走,汽车停。红灯为行人停,汽车走。那么只要实现观察者的(汽车和行人)当信号灯发生变化时的update 行为即可。被观察者添加了行人和汽车的观察者对象,当被观察者发生变化时,通知观察者做出反映。

[《JAVA与模式》之观察者模式][https://www.cnblogs.com/java-my-life/archive/2012/05/16/2502279.html]