内容简介:先来说下观察者模式,其实在Android开发中,我们使用观察者模式的时候还是非常多的,无论是广播使用的发布-订阅模式,还是listview的notifyDataSetChanged,或者是RxJava的使用,都是观察者模式的运用。今天就来重新看一下观察者模式。订阅模式,又称观察者模式。定义对象间一种一对多的依赖关系(注册),使得当一个对象改变状态后,则所有依赖它的对象都会得到通知并被自动更新(通知)。说白了就是个注册,通知的过程。先来定下一种场景,我每个月都会在微信上,收到房东的房租单(跪舔有房的大佬们),
先来说下观察者模式,其实在Android开发中,我们使用观察者模式的时候还是非常多的,无论是广播使用的发布-订阅模式,还是listview的notifyDataSetChanged,或者是RxJava的使用,都是观察者模式的运用。今天就来重新看一下观察者模式。
概念
订阅模式,又称观察者模式。定义对象间一种一对多的依赖关系(注册),使得当一个对象改变状态后,则所有依赖它的对象都会得到通知并被自动更新(通知)。说白了就是个注册,通知的过程。
观察者模式的UML图
- Subject主题,也就是被观察者Observable
简单使用观察者模式
先来定下一种场景,我每个月都会在微信上,收到房东的房租单(跪舔有房的大佬们),假设房东是被观察者,我是观察者,租客我把联系方式留给房东这就是一个注册过程,每个月月初,房东大佬都会通知我们交租(被观察者变化通知观察者)。
JDK为我们提供了Observer与Observable内置接口,我们可以很方便的使用它们。
public class TheOppressed implements Observer { public String name; public TheOppressed(String name){ this.name = name; } @Override public void update(Observable o, Object arg) { System.out.println(name +" 收到要交租信息"); } } public class Exploiter extends Observable { public void postChange(String content){ setChanged(); notifyObservers(content); } } public static void main(String[] args) { Exploiter exploiter = new Exploiter(); TheOppressed theOppressed1 = new TheOppressed("打工仔1"); TheOppressed theOppressed2 = new TheOppressed("打工仔2"); TheOppressed theOppressed3 = new TheOppressed("打工仔3"); exploiter.addObserver(theOppressed1); exploiter.addObserver(theOppressed2); exploiter.addObserver(theOppressed3); exploiter.postChange("打工仔们快来交房租啦~~"); } 复制代码
其实往往观察者模式我们自己也可以来写,而不用系统提供的方式
/** * 观察者 */ public interface MyObserver { /** * 找我 * @param content 找我啥事 */ void callMe(String content); } /** * 被观察者 */ public interface MySubject { /** * 观察者注册 */ void registerObserver(MyObserver observer); /** * 删除观察者 */ void removeObserver(MyObserver observer); /** * 主题有变化时通知观察者 */ void notifyObserver(); } 复制代码
先定义两个接口,实现了观察者模式的基本方法
/** * 房东 */ public class Exploiter implements MySubject { private String name; //存放观察者的集合 private List<MyObserver> observers; private String content; public Exploiter(String name) { this.name = name; observers = new ArrayList<>(); content = ""; } @Override public void registerObserver(MyObserver observer) { this.observers.add(observer); } @Override public void removeObserver(MyObserver observer) { int index = observers.indexOf(observer); if (index > 0) { observers.remove(observer); } } @Override public void notifyObserver() { for (int i = 0; i < observers.size(); i++) { //遍历所有房客,并调用他们的通知方法 MyObserver observer = observers.get(i); observer.callMe(content); } } /** * 房东要发信息啦 */ public void postMessage(String content) { if (null != content && !content.trim().equals("")) { this.content = content; this.notifyObserver(); } } } /** * 劳动人民 */ public class TheOppressed implements MyObserver { public String name; public TheOppressed(String name) { this.name = name; } @Override public void callMe(String content) { System.out.println(name + " " + content); } } 复制代码
调用代码用下
public class Test { public static void main(String[] args) { Exploiter exploiter = new Exploiter("房东"); TheOppressed theOppressed1 = new TheOppressed("打工仔1"); TheOppressed theOppressed2 = new TheOppressed("打工仔2"); TheOppressed theOppressed3 = new TheOppressed("打工仔3"); exploiter.registerObserver(theOppressed1); exploiter.registerObserver(theOppressed2); exploiter.registerObserver(theOppressed3); exploiter.postMessage("打工仔们快来交房租啦~~"); } } //结果 打工仔1 打工仔们快来交房租啦~~ 打工仔2 打工仔们快来交房租啦~~ 打工仔3 打工仔们快来交房租啦~~ 复制代码
其实在JDK9之后,Observer 这些都已经被废弃了,主要因为它
- 不可序列化,Observable是个类,而不是一个接口,没有实现Serializable,所以,不能序列化和它的子类
- 没有线程安全,方法可以被其子类覆盖,并且事件通知可以以不同的顺序并且可能在不同的线程上发生。
- 可以使用PropertyChangeEvent和PropertyChangeListener,它是java.beans包下的类
手写线程安全的观察者模式
其实我们从上面很容易看出来,多观察者需要串行调用,被观察者发生动作,观察者要作出回应,如果观察霆太多,而且处理时间长怎么办?用异步,也许你会脱口而出,那么,异步的处理就要考虑到线程安全和队列的问题。
就是刚刚同样的场景,如果房东先发了一条涨租200,后来又发了一条,收房租(当然要多准备200),假设延迟性是非常的大的情况下,我们不可能单线程串行一直等,太费性能了,开了多线程的情况下,那么就会出现问题,可能某人会先收到交房租,这样就乱了。
现实中有好多这种并发场景,一个或者多个线程,要等待另一组线程执行完成后,才能继续执行的问题,jdk已的com.util.concurrent下为我们提供了很多多线程的类,以后有机会再细讲,今天先用一个, CountDownLatch
。
CountDownLatch
CountDownLatch就一个线程同步工具,它相当于一个倒序计数器,用来协调多个线程的执行。多个线程通过调用它们所共享的计数器CountDownLatch的countDown方法来让计数器减1。通过await方法来阻塞当前线程,直到计数器变成0。达到线程阻塞直至其他线程执行完成被重新唤醒。主要有三个方法:
- 构造函数,初始化state的值,state等于同步线程数
- await(),让线程阻塞
- countDown(),计数器(state)减1的方法。
概念抄完,直接下手
先来定义一下Observer与Subject的接口
/** * 被观察者 */ public interface MySubject { /** * 观察者注册 */ void registerObserver(MyObserver observer); /** * 删除观察者 */ void removeObserver(MyObserver observer); /** * 主题有变化时通知观察者 */ void notifyObserver(); } /** * 观察者 */ public interface MyObserver { /** * 找我 * @param content 找我啥事 */ void callMe(String content); /** * 观察者名字 */ String getName(); } 复制代码
然后可以实现租客与房东的实现类
/** * 房东 */ public class Exploiter implements MySubject { private String name; // private List<MyObserver> observers; private ConcurrentMap<String, MyObserver> observers; private String content; public Exploiter(String name) { this.name = name; // observers = new ArrayList<>(); observers = new ConcurrentHashMap<>(); content = ""; } @Override public void registerObserver(MyObserver observer) { // this.observers.add(observer); this.observers.put(observer.getName(), observer); } @Override public void removeObserver(MyObserver observer) { observers.remove(observer.getName()); // int index = observers.indexOf(observer); // if (index > 0) { // observers.remove(observer); // } } @Override public void notifyObserver() { try { long beginTime = System.currentTimeMillis(); CountDownLatch latch = new CountDownLatch(observers.size()); int i = 0; for (MyObserver observer : observers.values()) { MessageSending messageSending = new MessageSending(latch, observer, content); messageSending.start(); i++; } // for (int i = 0; i < observers.size(); i++) { // MyObserver observer = observers.get(i); // observer.callMe(content); // } latch.await(); long endTime = System.currentTimeMillis(); System.out.println(name + "消息发送完毕,耗时:" + (endTime - beginTime)); System.out.println(); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 房东要发信息啦 */ public void postMessage(String content) { if (null != content && !content.trim().equals("")) { this.content = content; System.out.println(name + "发布信息:" + content); this.notifyObserver(); } } } /** * 劳动人民 */ public class TheOppressed implements MyObserver { public String name; public TheOppressed(String name) { this.name = name; } @Override public void callMe(String content) { System.out.println(name + "接收到通知:" + content); } @Override public String getName() { return name; } } 复制代码
最后在main方法调用如下:
public class Test { public static void main(String[] args) { Exploiter exploiter = new Exploiter("房东"); TheOppressed theOppressed1 = new TheOppressed("打工仔1"); TheOppressed theOppressed2 = new TheOppressed("打工仔2"); TheOppressed theOppressed3 = new TheOppressed("打工仔3"); exploiter.registerObserver(theOppressed1); exploiter.registerObserver(theOppressed2); exploiter.registerObserver(theOppressed3); exploiter.postMessage("这个月房租加200!!!"); exploiter.postMessage("打工仔们快来交房租啦~~"); } } 复制代码
可以看到输出结果为:
房东发布信息:这个月房租加200!!! 打工仔2接收到通知:这个月房租加200!!! 打工仔1接收到通知:这个月房租加200!!! 往打工仔1消息发送完毕 往打工仔2消息发送完毕 打工仔3接收到通知:这个月房租加200!!! 往打工仔3消息发送完毕 房东消息发送完毕,耗时:2004 房东发布信息:打工仔们快来交房租啦~~ 打工仔1接收到通知:打工仔们快来交房租啦~~ 往打工仔1消息发送完毕 打工仔2接收到通知:打工仔们快来交房租啦~~ 往打工仔2消息发送完毕 打工仔3接收到通知:打工仔们快来交房租啦~~ 往打工仔3消息发送完毕 房东消息发送完毕,耗时:2001 复制代码
可以看到,使用多线程往观察者发送信息,观察者都可以很迅速的接收到信息,这是并行的,但是又保证了被观察者的多个消息之间是有先后顺序的。
总结
观察者模式主要是对象的解耦,将观察者与被观察者之间完全隔离。jdk提供的默认观察者Observer/Observable在多线程下有安全性问题,需要自己手写,JDK9之后已经废弃了。
PS:今天看的比较简单,却也挺重要吧,以前从来没注意过关于观察者模式的 多线程安全性问题,也算是查漏补缺吧。
参考
《Android源码设计模式》
观察者模式(二)——多线程与CountDownLatch浅析
下面是我的公众号,欢迎大家关注我
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Excel图表之道
刘万祥 / 电子工业出版社 / 2010年4月 / 59.00元
本书介绍作者在实践工作中总结出来的一套“杂志级商务图表沟通方法”,告诉读者如何设计和制作达到杂志级质量的、专业有效的商务图表,作者对诸如《商业周刊》、《经济学人》等全球顶尖商业杂志上的精彩图表案例进行分析,给出其基于Excel的实现方法,包括数据地图、动态图表、仪表板等众多高级图表技巧。 本书提供大量图表模板源文件,包括详细的制作步骤,提供网上下载。提供博客支持。 本书定位于中高级Ex......一起来看看 《Excel图表之道》 这本书的介绍吧!