设计模式(三)--观察者模式
最近一直没有什么状态,无心学习,业余时间不能再宅在家里了,宅男都有神经病,整天对着手机说话,不出门,没朋友,我说的对吗?siri?博客不定期更新,爱看不看,反正也没几个人看。还有,男人都要像灭霸一样,即使是反派,也要光明磊落,说不害谁就不害谁,更何况是队友呢?
记录一下观察者模式,这个模式应该是项目中最可能用到的模式之一了,话不多说,进入正题。
什么是观察者模式,我自己理解,就是一个类管理着所有依赖于它的观察者类,并且它状态变化时会主动给这些依赖它的类发出通知。
下面给出观察者模式的类图。
我们根据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(); } }
打印结果如下。
其实观察者是被动接收通知的,是不是能作为观察者,能不能接收通知,完全是被观察者决定。
在网上看到一个不错的例子,写在这里。我们经常看的小说网站,都有这样的功能,就是读者可以订阅作者,作者一旦有什么新书发表,就要通知已经订阅他的读者,这是一个典型的观察者模式的应用场景。
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("观察者模式"); } }
结果如下。
观察者模式差不多就这样,后续项目中遇到实际的例子,可以补充在后面。