JAVA中的观察者模式实例教程

简介:

观察者模式是一种行为设计模式。观察者模式的用途是,当你对一个对象的状态感兴趣,希望在它每次发生变化时获得通知。在观察者模式中,观察另外一个对象状态的对象叫做Observer观察者,被观察的对象叫着Subject被观察者。根据GoF规则,观察者模式的意图是:

定义对象之间一对多的依赖关系,一个对象状态改变,其他相关联的对象就会得到通知并被自动更新。


Subject(被观察者)包含了一些需要在其状态改变时通知的观察者。因此,他应该提供给观察者可以register(注册)自己和unregister(注销)自己的方法。当Subject(被观察者)发生变化的时候,也需要包含一个方法来通知所有观察者。当通知观察者的时候,可以推送更新内容,或者提供另外一个方法来获得更新内容。

观察者应该有一种方法,这种方法能够设置观察者对象并且可以由被观察者使用来通知其更新。

JAVA提供了内置的方式来实现观察者模式,java.util.Observablejava.util.Observer接口。然而他们用的不是很广泛。因为此实现过于简单,大多数时候我们都不想最后扩展的类仅仅是实现了观察者模式,因为JAVA类不能多继承。

Java Messages Service(JMS)消息服务使用观察者模式与命令模式来实现不同的程序之间的数据的发布和订阅。

MVC模型-视图-控制框架也使用观察者模式,把模型当做被观察者,视图视为观察者。视图能够注册自己到模型上来获得模型的改变。

观察者模式例子

在此例中,我们将完成一个简单的主题讨论,观察者能够注册此主题。任何在此主题上的内容提交导致的变化都会通知所有在注册的观察者。

基于Subject被观察者的需求,这个是实现一个基本的Subject接口,此接口定了一系列具体的方法需要在随后实现接口的具体类中被实现。

01 package com.journaldev.design.observer;
02  
03 public interface Subject {
04  
05     //methods to register and unregister observers
06     public void register(Observer obj);
07     public void unregister(Observer obj);
08  
09     //method to notify observers of change
10     public void notifyObservers();
11  
12     //method to get updates from subject
13     public Object getUpdate(Observer obj);
14  
15 }

现在创建一个相关联的观察者。它需要有一个方法能使Subject附属于一个观察者。另外的方法能够接受Subject的变化通知。

01 package com.journaldev.design.observer;
02  
03 public interface Observer {
04  
05     //method to update the observer, used by subject
06     public void update();
07  
08     //attach with subject to observe
09     public void setSubject(Subject sub);
10 }

这种关联已经建立。现在实现具体的主题。

01 package com.journaldev.design.observer;
02  
03 import java.util.ArrayList;
04 import java.util.List;
05  
06 public class MyTopic implements Subject {
07  
08     private List<Observer> observers;
09     private String message;
10     private boolean changed;
11     private final Object MUTEX= new Object();
12  
13     public MyTopic(){
14         this.observers=new ArrayList<>();
15     }
16     @Override
17     public void register(Observer obj) {
18         if(obj == nullthrow new NullPointerException("Null Observer");
19         if(!observers.contains(obj)) observers.add(obj);
20     }
21  
22     @Override
23     public void unregister(Observer obj) {
24         observers.remove(obj);
25     }
26  
27     @Override
28     public void notifyObservers() {
29         List<Observer> observersLocal = null;
30         //synchronization is used to make sure any observer registered after message is received is not notified
31         synchronized (MUTEX) {
32             if (!changed)
33                 return;
34             observersLocal = new ArrayList<>(this.observers);
35             this.changed=false;
36         }
37         for (Observer obj : observersLocal) {
38             obj.update();
39         }
40  
41     }
42  
43     @Override
44     public Object getUpdate(Observer obj) {
45         return this.message;
46     }
47  
48     //method to post message to the topic
49     public void postMessage(String msg){
50         System.out.println("Message Posted to Topic:"+msg);
51         this.message=msg;
52         this.changed=true;
53         notifyObservers();
54     }
55  
56 }

注册与注销观察者方法的实现非常简单,额外的方法postMessage()将被客户端应用来提交一个字符串消息给此主题。注意,布尔变量用于追踪主题状态的变化并且通知观察者此种变化。这个变量是必须的,因为如果没有更新,但是有人调用notifyObservers()方法,他就不能发送错误的通知信息给观察者。

此外需要注意的是,notifyObservers()中使用synchronization同步的方式来确保在消息被发布给主题之前,通知只能被发送到注册的观察者处。

此处是观察者的实现。他们将一直关注subject对象。

01 package com.journaldev.design.observer;
02  
03 public class MyTopicSubscriber implements Observer {
04  
05     private String name;
06     private Subject topic;
07  
08     public MyTopicSubscriber(String nm){
09         this.name=nm;
10     }
11     @Override
12     public void update() {
13         String msg = (String) topic.getUpdate(this);
14         if(msg == null){
15             System.out.println(name+":: No new message");
16         }else
17         System.out.println(name+":: Consuming message::"+msg);
18     }
19  
20     @Override
21     public void setSubject(Subject sub) {
22         this.topic=sub;
23     }
24  
25 }

注意,update()方法的实现使用了被观察者的getUpdate()来处理更新的消息。此处应该避免把消息作为参数传递给update()方法。

一下为简单地测试程序来验证话题类的实现。

01 package com.journaldev.design.observer;
02  
03 public class ObserverPatternTest {
04  
05     public static void main(String[] args) {
06         //create subject
07         MyTopic topic = new MyTopic();
08  
09         //create observers
10         Observer obj1 = new MyTopicSubscriber("Obj1");
11         Observer obj2 = new MyTopicSubscriber("Obj2");
12         Observer obj3 = new MyTopicSubscriber("Obj3");
13  
14         //register observers to the subject
15         topic.register(obj1);
16         topic.register(obj2);
17         topic.register(obj3);
18  
19         //attach observer to subject
20         obj1.setSubject(topic);
21         obj2.setSubject(topic);
22         obj3.setSubject(topic);
23  
24         //check if any update is available
25         obj1.update();
26  
27         //now send message to subject
28         topic.postMessage("New Message");
29     }
30  
31 }

此处为上述输出内容:

1 Obj1:: No new message
2 Message Posted to Topic:New Message
3 Obj1:: Consuming message::New Message
4 Obj2:: Consuming message::New Message
5 Obj3:: Consuming message::New Message</pre>

观察者模式的UML图
observer-pattern

观察者模式也被叫做发布订阅模式。JAVA中的一些具体应用如下:

  • Swing 中的 java.util.EventListener
  • javax.servlet.http.HttpSessionBindingListener
  • javax.servlet.http.HttpSessionAttributeListener

以上为全部的观察者模式。希望你已经喜欢上它了。在评论中分享你的感受或者请分享给其他人。

相关文章
|
3天前
|
Java
java中递归实例
java中递归实例
15 0
|
5天前
|
Web App开发 JavaScript 前端开发
《手把手教你》系列技巧篇(三十九)-java+ selenium自动化测试-JavaScript的调用执行-上篇(详解教程)
【5月更文挑战第3天】本文介绍了如何在Web自动化测试中使用JavaScript执行器(JavascriptExecutor)来完成Selenium API无法处理的任务。首先,需要将WebDriver转换为JavascriptExecutor对象,然后通过executeScript方法执行JavaScript代码。示例用法包括设置JS代码字符串并调用executeScript。文章提供了两个实战场景:一是当时间插件限制输入时,用JS去除元素的readonly属性;二是处理需滚动才能显示的元素,利用JS滚动页面。还给出了一个滚动到底部的代码示例,并提供了详细步骤和解释。
28 10
|
12天前
|
Java 测试技术 Python
《手把手教你》系列技巧篇(三十六)-java+ selenium自动化测试-单选和多选按钮操作-番外篇(详解教程)
【4月更文挑战第28天】本文简要介绍了自动化测试的实战应用,通过一个在线问卷调查(&lt;https://www.sojump.com/m/2792226.aspx/&gt;)为例,展示了如何遍历并点击问卷中的选项。测试思路包括找到单选和多选按钮的共性以定位元素,然后使用for循环进行点击操作。代码设计方面,提供了Java+Selenium的示例代码,通过WebDriver实现自动答题。运行代码后,可以看到控制台输出和浏览器的相应动作。文章最后做了简单的小结,强调了本次实践是对之前单选多选操作的巩固。
24 0
|
15天前
|
Java
【专栏】Java反射机制,该机制允许程序在运行时获取类信息、动态创建对象、调用方法和访问属性
【4月更文挑战第27天】本文探讨了Java反射机制,该机制允许程序在运行时获取类信息、动态创建对象、调用方法和访问属性。反射通过Class、Constructor、Method和Field类实现。文中列举了反射的应用场景,如动态创建对象、调用方法、访问属性和处理注解,并提供了相关实例代码演示。
|
2天前
|
前端开发 Java 测试技术
《手把手教你》系列技巧篇(四十二)-java+ selenium自动化测试 - 处理iframe -下篇(详解教程)
【5月更文挑战第6天】本文介绍了如何使用Selenium处理含有iframe的网页。作者首先解释了iframe是什么,即HTML中的一个框架,用于在一个页面中嵌入另一个页面。接着,通过一个实战例子展示了在QQ邮箱登录页面中,由于输入框存在于iframe内,导致直接定位元素失败。作者提供了三种方法来处理这种情况:1)通过id或name属性切换到iframe;2)使用webElement对象切换;3)通过索引切换。最后,给出了相应的Java代码示例,并提醒读者根据iframe的实际情况选择合适的方法进行切换和元素定位。
7 0
|
3天前
|
Java
代码实例演示Java字符串与输入流互转
代码实例演示Java字符串与输入流互转
|
3天前
|
前端开发 测试技术 Python
《手把手教你》系列技巧篇(四十一)-java+ selenium自动化测试 - 处理iframe -上篇(详解教程)
【5月更文挑战第5天】本文介绍了HTML中的`iframe`标签,它用于在网页中嵌套其他网页。`iframe`常用于加载外部内容或网站的某个部分,以实现页面美观。文章还讲述了使用Selenium自动化测试时如何处理`iframe`,通过`switchTo().frame()`方法进入`iframe`,完成相应操作,然后使用`switchTo().defaultContent()`返回主窗口。此外,文章提供了一个包含`iframe`的HTML代码示例,并给出了一个简单的自动化测试代码实战,演示了如何在`iframe`中输入文本。
14 3
|
4天前
|
JavaScript 前端开发 Java
《手把手教你》系列技巧篇(四十)-java+ selenium自动化测试-JavaScript的调用执行-下篇(详解教程)
【5月更文挑战第4天】本文介绍了如何使用JavaScriptExecutor在自动化测试中实现元素高亮显示。通过创建并执行JS代码,可以改变元素的样式,例如设置背景色和边框,以突出显示被操作的元素。文中提供了一个Java示例,展示了如何在Selenium中使用此方法,并附有代码截图和运行效果展示。该技术有助于跟踪和理解测试过程中的元素交互。
8 0
|
5天前
|
JavaScript 前端开发 测试技术
《手把手教你》系列技巧篇(三十八)-java+ selenium自动化测试-日历时间控件-下篇(详解教程)
【5月更文挑战第2天】在自动化测试过程中,经常会遇到处理日期控件的点击问题。宏哥之前分享过一种方法,但如果输入框是`readonly`属性,这种方法就无法奏效了。不过,通过修改元素属性,依然可以实现自动化填写日期。首先,定位到日期输入框并移除`readonly`属性,然后使用`sendKeys`方法输入日期。这样,即使输入框设置了`readonly`,也能成功处理日期控件。
24 1
|
7天前
|
Java 测试技术 Python
《手把手教你》系列技巧篇(三十七)-java+ selenium自动化测试-日历时间控件-上篇(详解教程)
【5月更文挑战第1天】该文介绍了使用Selenium自动化测试网页日历控件的方法。首先,文章提到在某些Web应用中,日历控件常用于选择日期并筛选数据。接着,它提供了两个实现思路:一是将日历视为文本输入框,直接输入日期;二是模拟用户交互,逐步选择日期。文中给出了JQueryUI网站的一个示例,并展示了对应的Java代码实现,包括点击日历、选择日期等操作。
30 0