观察者模式

  最近一直没有什么状态,无心学习,业余时间不能再宅在家里了,宅男都有神经病,整天对着手机说话,不出门,没朋友,我说的对吗?siri?博客不定期更新,爱看不看,反正也没几个人看。还有,男人都要像灭霸一样,即使是反派,也要光明磊落,说不害谁就不害谁,更何况是队友呢?

  记录一下观察者模式,这个模式应该是项目中最可能用到的模式之一了,话不多说,进入正题。

  什么是观察者模式,我自己理解,就是一个类管理着所有依赖于它的观察者类,并且它状态变化时会主动给这些依赖它的类发出通知。

  下面给出观察者模式的类图。

java观察者模式视频 观察者模式uml类图_System

  我们根据UML图翻译成java代码,首先是观察者接口。

/**
 * 观察者接口
 * @author xiaodongdong
 * @create 2018-05-16 17:02
 **/
public interface Observer {
    void update(Observable o);
}

  接下来是具体的观察者。

/**
 * 具体观察者1
 * @author xiaodongdong
 * @create 2018-05-16 17:04
 **/
public class ConcreteObserver1 implements Observer {
    @Override
    public void update(Observable o) {
        System.out.println("观察者1观察到" + o.getClass().getSimpleName() + "发生变化");
        System.out.println("观察者1做出相应");
    }
}
/**
 * 具体观察者2
 * @author xiaodongdong
 * @create 2018-05-16 17:05
 **/
public class ConcreteObserver2 implements Observer {
    @Override
    public void update(Observable o) {
        System.out.println("观察者2观察到" + o.getClass().getSimpleName() + "发生变化");
        System.out.println("观察者2做出相应");
    }
}

  接下来是被观察者,被观察者持有一个观察者的集合,一旦被观察者发生变化,会主动调用观察者的接口方法通知相关观察者。

import java.util.ArrayList;
import java.util.List;

/**
 * 被观察者
 * @author xiaodongdong
 * @create 2018-05-16 17:06
 **/
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);
        }
    }
}

  测试一下。

/**
 * 测试
 * @author xiaodongdong
 * @create 2018-05-16 17:09
 **/
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();
    }
}

  打印结果如下。

java观察者模式视频 观察者模式uml类图_java_02

  其实观察者是被动接收通知的,是不是能作为观察者,能不能接收通知,完全是被观察者决定。

  在网上看到一个不错的例子,写在这里。我们经常看的小说网站,都有这样的功能,就是读者可以订阅作者,作者一旦有什么新书发表,就要通知已经订阅他的读者,这是一个典型的观察者模式的应用场景。

  JDK已经帮我们写好了观察者接口和被观察者类,LZ列举一下JDK的关键实现,熟悉一下JDK的源码。

  首先是观察者接口。

/**
 * 观察者接口,每一个观察者都必须实现这个接口
 */
public interface Observer {
    //除了依赖被观察者,还提供了一个预留参数
    void update(Observable o, Object arg);
}

  再是被观察者类。

package java.util.Observable;

import java.util.Observer;
import java.util.Vector;

//被观察者类
public class Observable {
    //用来表示被观察者有没有改变
    private boolean changed = false;
    //观察者列表
    private Vector<java.util.Observer> obs;


    public Observable() {
        obs = new Vector<>();
    }

    //添加观察者,添加时会去重
    public synchronized void addObserver(java.util.Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    //删除观察者
    public synchronized void deleteObserver(java.util.Observer o) {
        obs.removeElement(o);
    }

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

  看注释应该能理解的差不多,但是这块有不足的地方,就是通知所有观察的地方,

for (int i = arrLocal.length-1; i>=0; i--)
    ((Observer)arrLocal[i]).update(this, arg);

  在循环遍历观察者时,JDK没有处理update可能抛出的异常,假设有一个update方法抛出异常,那么剩下的观察者就都得不到通知了。所以我觉得这块代码可以优化成这样。

for (int i = arrLocal.length-1; i>=0; i--){
    try {
        ((Observer)arrLocal[i]).update(this, arg);
    } catch (Throwable e) {
        e.printStackTrace();
    }
}

  我们自己做的时候,需要保证读者类中的update方法不会抛异常。JDK的观察者模式源码差不多就长这样,我们用JDK的实现把上面那个读者订阅作者的例子完成。首先要搞明白这个例子里面谁是观察者,谁是被观察者,很明显,读者是观察者,作者是被观察者,除此之外,我们还需要一个管理器来帮我们管理作者的列表,我们逐一实现,首先是读者类。

import java.util.Observable;
import java.util.Observer;

/**
 * 读者
 * @author xiaodongdong
 * @create 2018-05-17 15:55
 **/
public class Reader implements Observer {

    private String name;

    public Reader(String name) {
        super();
        this.name = name;
    }

    public String getName() {
        return name;
    }

    //关注作者
    public void subscribe(String writerName){
        WriterManager.getInstance().getWriter(writerName).addObserver(this);
    }

    //取关作者
    public void unsubscribe(String writerName){
        WriterManager.getInstance().getWriter(writerName).deleteObserver(this);
    }

    //作者发表新书时调用
    @Override
    public void update(Observable o, Object obj) {
        if (o instanceof Writer) {
            Writer writer = (Writer) o;
            System.out.println(name+"得到通知====" + writer.getName() + "发布了新书《" + writer.getLastNovel() + "》");
        }
    }

}

 

  下面是作者类。

//作者类,要继承自被观察者类
public class Writer extends Observable{
    
    private String name;//作者的名称
    
    private String lastNovel;//记录作者最新发布的小说

    public Writer(String name) {
        super();
        this.name = name;
        WriterManager.getInstance().add(this);
    }

    //作者发布新小说了,要通知所有关注自己的读者
    public void addNovel(String novel) {
        System.out.println(name + "发布了新书《" + novel + "》!");
        lastNovel = novel;
        setChanged();
        notifyObservers();
    }
    
    public String getLastNovel() {
        return lastNovel;
    }

    public String getName() {
        return name;
    }

}

  作者管理器,关键作用无非是快速查找作者,组合Map实现。

import java.util.HashMap;
import java.util.Map;

/**
 * 作者管理器
 * @author xiaodongdong
 * @create 2018-05-17 15:56
 **/
public class WriterManager{

    private Map<String, Writer> writerMap = new HashMap<String, Writer>();

    //添加作者
    public void add(Writer writer){
        writerMap.put(writer.getName(), writer);
    }
    //根据作者姓名获取作者 可优化 作者名字可能重复
    public Writer getWriter(String name){
        return writerMap.get(name);
    }

    //单例
    private WriterManager(){}

    public static WriterManager getInstance(){
        return WriterManagerInstance.instance;
    }
    private static class WriterManagerInstance{
        private static WriterManager instance = new WriterManager();
    }
}

  测试一下。

/**
 * 测试
 * @author xiaodongdong
 * @create 2018-05-17 16:05
 **/
public class Client {
    public static void main(String[] args) {
        //假设四个读者,两个作者
        Reader r1 = new Reader("读者1");
        Reader r2 = new Reader("读者2");
        Reader r3 = new Reader("读者3");
        Reader r4 = new Reader("读者4");
        Writer w1 = new Writer("作者1");
        Writer w2 = new Writer("作者2");
        //四人关注了作者1
        r1.subscribe("作者1");
        r2.subscribe("作者1");
        r3.subscribe("作者1");
        r4.subscribe("作者1");
        //读者3和读者4还关注了作者2
        r3.subscribe("作者2");
        r4.subscribe("作者2");

        //作者发布新书就会通知关注的读者
        //作者1写了设计模式
        w1.addNovel("设计模式");
        //作者2写了JAVA编程思想
        w2.addNovel("JAVA编程思想");
        //读者1取关作者1
        r1.unsubscribe("作者1");
        //作者1再写书将不会通知读者1
        w1.addNovel("观察者模式");
    }
}

  结果如下。

java观察者模式视频 观察者模式uml类图_java观察者模式视频_03

  观察者模式差不多就这样,后续项目中遇到实际的例子,可以补充在后面。