java设计模式 观察者模式_JAVA设计模式之观察者模式

一、定义

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

观察者模式结构图,如下图1-1所示:

796275af1e9819acc7bd421e3a7fe3f5.png

图 1-1

二、实例展示

Subjcet 类,可翻译为主题或抽象通知者,一般由一个抽象类或一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

1 abstract classSubject{2 private List observers = new ArrayList();3

4 //增加观察者

5 public voidAttach(){6 observers.Add(observer);7 }8

9 //移除观察者

10 public voidDetach(){11

12 }13 }

Observer类,抽象观察者,为所有的具体观察者定义了一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update()方法,这个方法叫做更新方法。

1 abstract classObserver{2 public abstract voidUpdate();3 }

ConcreteSubject类,叫做具体主题或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。

1 class ConcreteSubject extendsSubject{2 privateString subjectState;3

4 publicString getSubjectState() {5 returnsubjectState;6 }7

8 public voidsetSubjectState(String subjectState) {9 this.subjectState =subjectState;10 }11 }

ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。

1 class ConcreteObserver extendsObserver{2 privateString name;3 privateString observerState;4 privateConcreteSubject subject;5

6 publicConcreteObserver(ConcreteSubject subject,String name){7 this.subject =subject;8 this.name =name;9 }10

11 @override12 public voidUpdate(){13 observerState =subject.SubjectState;14 System.out.println("观察者"+name+"的新状态是"+observerState);15 }16

17 publicString getSubjectState() {18 returnsubjectState;19 }20

21 public voidsetSubjectState(String subjectState) {22 this.subjectState =subjectState;23 }24 }

客户端代码:

1 static voidMain(String[] args){2 ConcreteSubject s = newConcreteSubject();3

4 s.Attach(new ConcreteObserver(s,"X"));5 s.Attach(new ConcreteObserver(s,"Y"));6 s.Attach(new ConcreteObserver(s,"Z"));7

8 s.SubjectState = "ABC";9 s.Notify();10 }

结果显示:

1 观察者X的状态是ABC2 观察者Y的状态是ABC3 观察者Z的状态是ABC

三、使用场景

1、一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们各自独立地改变和复用。

2、当一个对象的改变需要同时改变其它对象的时候。

四、使用总结

1、观察者模式的主要优点如下:

1) 观察者模式可以实现表示层和逻辑层的分离,定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不用的表示层充当具体观察者角色。

2) 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者集合,无需了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。

3) 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。

4) 观察者模式满足"开闭原则"的要求,增加新的具体观察者对象无须修改原有系统代码。在具体观察者和观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。

2、观察者模式的主要缺点如下:

1) 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。

2) 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

3) 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

五、观察者模式的典型应用

1、JDK提供的观察者接口

观察者模式在Java语言中的地位非常重要。在JDK的java.util包中,提供了Observer类及Observer接口,它们构成了JDK对观察者模式的支持。

其中的Observer接口为观察者,只有一个update方法。当观察者目标发生变化时被调用,其代码如下:

1 public interfaceObserver{2 voidupdate(Observer o,Object arg);3 }

Observerable类则为目标类,相比示例中的Publisher类多了并发和NPE方面的考虑。

1 public classObservable {2 private boolean changed = false;3 private Vectorobs;4

5 /**Construct an Observable with zero Observers.*/

6

7 publicObservable() {8 obs = new Vector<>();9 }10

11 /**

12 * Adds an observer to the set of observers for this object, provided13 * that it is not the same as some observer already in the set.14 * The order in which notifications will be delivered to multiple15 * observers is not specified. See the class comment.16 *17 *@paramo an observer to be added.18 *@throwsNullPointerException if the parameter o is null.19 */

20 public synchronized voidaddObserver(Observer o) {21 if (o == null)22 throw newNullPointerException();23 if (!obs.contains(o)) {24 obs.addElement(o);25 }26 }27

28 /**

29 * Deletes an observer from the set of observers of this object.30 * Passing null to this method will have no effect.31 *@paramo the observer to be deleted.32 */

33 public synchronized voiddeleteObserver(Observer o) {34 obs.removeElement(o);35 }36

37 /**

38 * If this object has changed, as indicated by the39 * hasChanged method, then notify all of its observers40 * and then call the clearChanged method to41 * indicate that this object has no longer changed.42 *

43 * Each observer has its update method called with two44 * arguments: this observable object and null. In other45 * words, this method is equivalent to:46 *

47 * notifyObservers(null)
48 *49 *@seejava.util.Observable#clearChanged()50 *@seejava.util.Observable#hasChanged()51 *@seejava.util.Observer#update(java.util.Observable, java.lang.Object)52 */

53 public voidnotifyObservers() {54 notifyObservers(null);55 }56

57 /**

58 * If this object has changed, as indicated by the59 * hasChanged method, then notify all of its observers60 * and then call the clearChanged method to indicate61 * that this object has no longer changed.62 *

63 * Each observer has its update method called with two64 * arguments: this observable object and the arg argument.65 *66 *@paramarg any object.67 *@seejava.util.Observable#clearChanged()68 *@seejava.util.Observable#hasChanged()69 *@seejava.util.Observer#update(java.util.Observable, java.lang.Object)70 */

71 public voidnotifyObservers(Object arg) {72 /*

73 * a temporary array buffer, used as a snapshot of the state of74 * current Observers.75 */

76 Object[] arrLocal;77

78 synchronized (this) {79 /*We don't want the Observer doing callbacks into80 * arbitrary code while holding its own Monitor.81 * The code where we extract each Observable from82 * the Vector and store the state of the Observer83 * needs synchronization, but notifying observers84 * does not (should not). The worst result of any85 * potential race-condition here is that:86 * 1) a newly-added Observer will miss a87 * notification in progress88 * 2) a recently unregistered Observer will be89 * wrongly notified when it doesn't care90 */

91 if (!changed)92 return;93 arrLocal =obs.toArray();94 clearChanged();95 }96

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

101 /**

102 * Clears the observer list so that this object no longer has any observers.103 */

104 public synchronized voiddeleteObservers() {105 obs.removeAllElements();106 }107

108 /**

109 * Marks this Observable object as having been changed; the110 * hasChanged method will now return true.111 */

112 protected synchronized voidsetChanged() {113 changed = true;114 }115

116 /**

117 * Indicates that this object has no longer changed, or that it has118 * already notified all of its observers of its most recent change,119 * so that the hasChanged method will now return false.120 * This method is called automatically by the121 * notifyObservers methods.122 *123 *@seejava.util.Observable#notifyObservers()124 *@seejava.util.Observable#notifyObservers(java.lang.Object)125 */

126 protected synchronized voidclearChanged() {127 changed = false;128 }129

130 /**

131 * Tests if this object has changed.132 *133 *@returntrue if and only if the setChanged134 * method has been called more recently than the135 * clearChanged method on this object;136 * false otherwise.137 *@seejava.util.Observable#clearChanged()138 *@seejava.util.Observable#setChanged()139 */

140 public synchronized booleanhasChanged() {141 returnchanged;142 }143

144 /**

145 * Returns the number of observers of this Observable object.146 *147 *@returnthe number of observers of this object.148 */

149 public synchronized intcountObservers() {150 returnobs.size();151 }152 }

可以使用Observable类以及Observer接口来实现一个微信公众号示例。

增加一个通知类WechatNotice,用于推送通知的传递。

1 public classWechatNotice {2 privateString publisher;3 privateString articleName;4

5 publicWechatNotice(String publisher,String articleName){6 this.publisher =publisher;7 this.articleName =articleName;8 }9

10 publicString getPublisher() {11 returnpublisher;12 }13

14 public voidsetPublisher(String publisher) {15 this.publisher =publisher;16 }17

18 publicString getArticleName() {19 returnarticleName;20 }21

22 public voidsetArticleName(String articleName) {23 this.articleName =articleName;24 }25 }

然后改写 WeChatClient 和 WeChatAccounts,分别实现JDK的 Observer 接口和继承 Observable 类。

1 importjava.util.Observable;2 importjava.util.Observer;3

4 public class WeChatClient implementsObserver {5 privateString username;6 publicWeChatClient(String username) {7 this.username =username;8 }9

10 public voidupdate(Observable o, Object arg) {11 //WeChatAccounts weChatAccounts = (WeChatAccounts) o;

12 WechatNotice notice =(WechatNotice) arg;13 System.out.println(String.format("用户 接收到 微信公众号 的推送,文章标题为 ", username, notice.getPublisher(), notice.getArticleName()));14

15 }16 }

1 importjava.util.Observable;2

3 public class WeChatAccounts extendsObservable {4 privateString name;5

6 publicWeChatAccounts(String name) {7 this.name =name;8 }9

10 public voidpublishArticles(String articleName, String content) {11 System.out.println(String.format("\n微信公众号 发布了一篇推送,文章名称为 ,内容为 ", this.name, articleName, content));12 setChanged();13 notifyObservers(new WechatNotice(this.getName(), articleName));14 }15

16 publicString getName() {17 returnname;18 }19

20 public voidsetName(String name) {21 this.name =name;22 }23 }

测试:

1 public classTestMain {2 public static voidmain(String[] args){3 WeChatAccounts accounts = new WeChatAccounts("飞鹰");4

5 WeChatClient user1 = new WeChatClient("张三");6 WeChatClient user2 = new WeChatClient("李四");7 WeChatClient user3 = new WeChatClient("王五");8

9 accounts.addObserver(user1);10 accounts.addObserver(user2);11 accounts.addObserver(user3);12

13 accounts.publishArticles("设计模式 | Java设计模式之观察者模式及其应用场景", "观察者模式的内容...");14

15 accounts.deleteObserver(user1);16 accounts.publishArticles("设计模式 | Java设计模式之建筑者模式及其应用场景", "建筑者模式的内容....");17 }18 }

测试结果如下,可以发现结果如示例一致。

1 微信公众号 发布了一篇推送,文章名称为 ,内容为

2 用户 接收到 微信公众号 的推送,文章标题为

3 用户 接收到 微信公众号 的推送,文章标题为

4 用户 接收到 微信公众号 的推送,文章标题为

5

6 微信公众号 发布了一篇推送,文章名称为 ,内容为

7 用户 接收到 微信公众号 的推送,文章标题为

8 用户 接收到 微信公众号 的推送,文章标题为

2、Spring ApplicationContext 事件机制中的观察者模式。

spring的事件机制是从java的事件机制拓展而来,ApplicationContext 中事件处理是由 ApplicationEvent 类和 ApplicationListener 接口来提供的。如果一个Bean实现了 ApplicationListener 接口,并且已经发布到容器中去,每次 ApplicationContext 发布一个 ApplicationEvent 事件,这个Bean就会接到通知

1) ApplicationContext:事件源,其中的 publishEvent()方法用于触发容器事件

2) ApplicationEvent:事件本身,自定义事件需要继承该类,可以用来传递数据

3) ApplicationListener:事件监听器接口,事件的业务逻辑封装在监听器里面

使用 spring 事件机制重新实现示例

1 importorg.springframework.context.ApplicationEvent;2

3 public class WechatNotice extendsApplicationEvent {4

5 privateString publisher;6 privateString articleName;7

8 publicWechatNotice(Object source, String publisher, String articleName) {9 super(source);10 this.publisher =publisher;11 this.articleName =articleName;12 }13

14 publicString getPublisher() {15 returnpublisher;16 }17

18 public voidsetPublisher(String publisher) {19 this.publisher =publisher;20 }21

22 publicString getArticleName() {23 returnarticleName;24 }25

26 public voidsetArticleName(String articleName) {27 this.articleName =articleName;28 }29 }

1 importorg.springframework.context.ApplicationEvent;2 importorg.springframework.context.ApplicationListener;3

4 public class WeChatClient implementsApplicationListener {5

6 privateString username;7

8 publicWeChatClient(String username) {9 this.username =username;10 }11

12

13 public voidonApplicationEvent(ApplicationEvent event) {14 if (event instanceofWechatNotice) {15 WechatNotice notice =(WechatNotice) event;16 System.out.println(String.format("用户 接收到 微信公众号 的推送,文章标题为 ", username, notice.getPublisher(), notice.getArticleName()));17 }18 }19

20 public voidsetUsername(String username) {21 this.username =username;22 }23

24 }

1 importorg.springframework.beans.BeansException;2 importorg.springframework.context.ApplicationContext;3 importorg.springframework.context.ApplicationContextAware;4

5 public class WeChatAccounts implementsApplicationContextAware {6

7 privateApplicationContext ctx;8 privateString name;9

10 publicWeChatAccounts(String name) {11 this.name =name;12 }13

14 publicApplicationContext getCtx() {15 returnctx;16 }17

18 public voidsetCtx(ApplicationContext ctx) {19 this.ctx =ctx;20 }21

22 publicString getName() {23 returnname;24 }25

26 public voidsetName(String name) {27 this.name =name;28 }29

30 public void setApplicationContext(ApplicationContext applicationContext) throwsBeansException {31 this.ctx =applicationContext;32 }33

34 public voidpublishArticles(String articleName, String content) {35 System.out.println(String.format("\n微信公众号 发布了一篇推送,文章名称为 ,内容为 ", this.name, articleName, content));36 ctx.publishEvent(new WechatNotice(this.name, this.name, articleName));37 }38 }

在 resources 目录下创建 spring.xml 文件,填入下面的内容

1

2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

3 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

4

5

6

7

8

9

10

11

12

13

14

15

16

测试:

1 importorg.springframework.context.ApplicationContext;2 importorg.springframework.context.support.ClassPathXmlApplicationContext;3

4 public classTestMain {5 public static voidmain(String[] args) {6 ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");7

8 WeChatAccounts accounts = (WeChatAccounts) context.getBean("WeChatAccounts");9 accounts.setName("飞鹰");10 accounts.setApplicationContext(context);11

12 accounts.publishArticles("设计模式 | Java设计模式之建筑者模式及其应用场景", "观察者模式的内容...");13 }14 }

输出如下:

1 用户 接收到 微信公众号 的推送,文章标题为

2 用户 接收到 微信公众号 的推送,文章标题为

3 用户 接收到 微信公众号 的推送,文章标题为

在此示例中 ApplicationContext 对象的实际类型为 ClassPathXmlApplicationContext,其中的与 publishEvent 方法相关的主要代码如下:

1 privateApplicationEventMulticaster applicationEventMulticaster;2

3 public voidpublishEvent(ApplicationEvent event) {4 this.getApplicationEventMulticaster().multicastEvent(event);5 if (this.parent != null) {6 this.parent.publishEvent(event);7 }8 }9

10 ApplicationEventMulticaster getApplicationEventMulticaster() throwsIllegalStateException {11 return this.applicationEventMulticaster;12 }13

14 protected voidinitApplicationEventMulticaster() {15 ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();16 if (beanFactory.containsLocalBean("applicationEventMulticaster")) {17 this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class);18 } else{19 this.applicationEventMulticaster = newSimpleApplicationEventMulticaster(beanFactory);20 beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster);21 }22

23 }

其中的 SimpleApplicationEventMulticaster 如下,multicastEvent 方法主要是通过遍历 ApplicationListener(注册由 AbstractApplicationEventMulticaster 实现),使用线程池框架 Executor 来并发执行 ApplicationListener 的 onApplicationEvent 方法,与示例本质上是一致的

1 public class SimpleApplicationEventMulticaster extendsAbstractApplicationEventMulticaster {2 privateExecutor taskExecutor;3

4 public void multicastEvent(finalApplicationEvent event) {5 Iterator var2 = this.getApplicationListeners(event).iterator();6

7 while(var2.hasNext()) {8 final ApplicationListener listener =(ApplicationListener)var2.next();9 Executor executor = this.getTaskExecutor();10 if (executor != null) {11 executor.execute(newRunnable() {12 public voidrun() {13 listener.onApplicationEvent(event);14 }15 });16 } else{17 listener.onApplicationEvent(event);18 }19 }20

21 }22 }

五、观察者模式不足

尽管已经用了依赖倒转的原则,但是"抽象通知者"还是依赖"抽象观察者",也就是说,万一没有了抽象观察者这样的接口,该通知的功能就完不成了。另外就是每个具体观察者,不一定都是"更新"的方法要调用的,需要实现的功能根本风马牛不相及的。要实现该功能,请关注下一节的"java委托功能的实现"。

参考:

1、http://laijianfeng.org/2018/10/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%E5%8F%8A%E5%85%B8%E5%9E%8B%E5%BA%94%E7%94%A8/

2、大话设计模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值