java观察者订阅模式

java中什么叫”观察者设计模式”?

观察者模式(Observer Pattern),又被称为发布/订阅模式,它是软体设计模式中的一种。观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

在观察者模式中,一个目标物件(被观察者)管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知,这通常通过呼叫各观察者所提供的方法来实现。此种模式通常被用来实作事件处理系统。观察者模式有很多实现方式,从根本上说,该模式必须包含两个角色:观察者和被观察者。观察者和被观察对象之间的互动关系不能体现成类之间的直接调用,那样就将使观察者和被观察对象之间紧密的耦合起来,从根本上违反面向对象的设计的原则。在具体的实现中,我们需要面向接口编程,让被观察者管理观察者对象接口类型,然后调用接口方法更新观察者。

详细内容请参考《软件秘笈:设计模式那点事》,祝你早日学会设计模式!

观察者模式(Observer Pattern)

观察者模式又称为发布订阅模式。一个发布者对应多个订阅者,一旦发布者的状态发生改变时,订阅者将收到订阅事件。

先看看一个生活中的例子:

我们使用想浏览Java相关的文章,于是我们点击订阅了[Java专题],当[Java专题]有新文章发布就会推送给我们,当然其他人也可以订阅[Java专题]并收到[Java专题]的推送。这就是观察者。 定义对象间的一对多关系,当一个对象的状态发生变化时,所依赖于它的对象都得到通知并主动更新。在观察者模式中,多个订阅者成为观察者(Observer),被观察的对象成为目标(Subject)。

实现观察者模式的方法不只一种,但是以包含Subject与Observer接口的类设计的做法最常见。(Java API 内置观察者模式用的是Observer接口与Observable类)

观察者模式UML图:

先定义观察者模式的接口

在观察者模式的实现上,有推模式和拉模式两种方式。

上面例子中

void updateByPush(Object obj) 就是推模式;

void updateByPull(Subject subject)就是拉模式

java.util包内包含最基本的Observer接口与Observable类(其实对应的就是Subject类)

我们看一下Observer源码

我们看到update更新方法有两个参数:Observable、Object,可见Java API 内置观察者模式同时支持[拉]和[取]

我们再来看看Observable类源码

注意Observable是一个类,而不是接口,这有一定的局限性。因为如果某个类想同时具有Observable类和另一个超类的行为,就会陷入两难,毕竟Java不支持多重继承。

有点需要特别提一下的就是,Java API 内置的Observable需要调用一下 setChanged();观察者才能收到推送,我们看一下源码,发现notifyObservers方法里有判断changed的状态为true才去通知观察者。

我们自己实现观察者模式的时候是没有这一点的,那加上这一个标志位有什么好处?好处就是更灵活,Observable类只提供这个boolean值来表明是否发生变化,而不定义什么叫变化,因为每个业务中对变化的具体定义不一样,因此子类自己来判断是否变化;该变量既提供了一种抽象(变与不变),同时提供了一种观察者更新状态的可延迟加载,通过后面的notifyObservers方法分析可知观察者是否会调用update方法,依赖于changed变量,因此即使被观察者在逻辑上发生改变了,只要不调用setChanged,update是不会被调用的。如果我们在某些业务场景不需要频繁触发update,则可以适时调用setChanged方法来延迟刷新。

阿里云折扣快速入口

java观察者订阅模式

java设计模式-回调、事件监听器、观察者模式

转自( )

背景

关于设计模式,之前笔者写过工厂模式,最近在使用gava ListenableFuture时发现事件监听模型特别有意思,于是就把事件监听、观察者之间比较了一番,发现这是一个非常重要的设计模式,在很多框架里扮演关键的作用。

回调函数

为什么首先会讲回调函数呢?因为这个是理解监听器、观察者模式的关键。

什么是回调函数

所谓的回调,用于回调的函数。 回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数。 有这么一句通俗的定义:就是程序员A写了一段程序(程序a),其中预留有回调函数接口,并封装好了该程序。程序员B要让a调用自己的程序b中的一个方法,于是,他通过a中的接口回调自己b中的方法。

举个例子:

这里有两个实体:回调抽象接口、回调者(即程序a)

回调接口(ICallBack )

public interface ICallBack {

public void callBack();

}

回调者(用于调用回调函数的类)

public class Caller {

}

回调测试:

public static void main(String[] args) {

Caller call = new Caller();

call.call(new ICallBack(){

控制台输出:

start…

终于回调成功了!

end…

还有一种写法

或实现这个ICallBack接口类

class CallBackC implements ICallBack{

@Override

public void callBack() {

System.out.println(“终于回调成功了!”);

}

}

有没有发现这个模型和执行一个线程,Thread很像。 没错,Thread就是回调者,Runnable就是一个回调接口。

new Thread(new Runnable(){

@Override

public void run() {

System.out.println(“回调一个新线程!”);

}}).start();

Callable也是一个回调接口,原来一直在用。 接下来我们开始讲事件监听器

事件监听模式

什么是事件监听器

监听器将监听自己感兴趣的事件一旦该事件被触发或改变,立即得到通知,做出响应。例如:android程序中的Button事件。

java的事件监听机制可概括为3点:

java的事件监听机制涉及到 事件源,事件监听器,事件对象 三个组件,监听器一般是接口,用来约定调用方式

当事件源对象上发生操作时,它将会调用事件监听器的一个方法,并在调用该方法时传递事件对象过去

事件监听器实现类,通常是由开发人员编写,开发人员通过事件对象拿到事件源,从而对事件源上的操作进行处理

举个例子

这里我为了方便,直接使用jdk,EventListener 监听器,感兴趣的可以去研究下源码,非常简单。

监听器接口

public interface EventListener extends java.util.EventListener {

//事件处理

public void handleEvent(EventObject event);

}

事件对象

public class EventObject extends java.util.EventObject{

private static final long serialVersionUID = 1L;

public EventObject(Object source){

super(source);

}

public void doEvent(){

System.out.println(“通知一个事件源 source :”+ this.getSource());

}

}

事件源

事件源是事件对象的入口,包含监听器的注册、撤销、通知

public class EventSource {

//监听器列表,监听器的注册则加入此列表

private VectorEventListener ListenerList = new VectorEventListener();

//注册监听器

public void addListener(EventListener eventListener){

ListenerList.add(eventListener);

}

//撤销注册

public void removeListener(EventListener eventListener){

ListenerList.remove(eventListener);

}

//接受外部事件

public void notifyListenerEvents(EventObject event){

for(EventListener eventListener:ListenerList){

eventListener.handleEvent(event);

}

}

}

测试执行

public static void main(String[] args) {

EventSource eventSource = new EventSource();

}

控制台显示:

通知一个事件源 source :openWindows

通知一个事件源 source :openWindows

doOpen something…

到这里你应该非常清楚的了解,什么是事件监听器模式了吧。 那么哪里是回调接口,哪里是回调者,对!EventListener是一个回调接口类,handleEvent是一个回调函数接口,通过回调模型,EventSource 事件源便可回调具体监听器动作。

有了了解后,这里还可以做一些变动。 对特定的事件提供特定的关注方法和事件触发

public class EventSource {

public void onCloseWindows(EventListener eventListener){

System.out.println(“关注关闭窗口事件”);

ListenerList.add(eventListener);

}

}

public static void main(String[] args) {

EventSource windows = new EventSource();

/**

* 另一种实现方式

*/

//关注关闭事件,实现回调接口

windows.onCloseWindows(new EventListener(){

}

这种就类似于,我们的窗口程序,Button监听器了。我们还可以为单击、双击事件定制监听器。

观察者模式

什么是观察者模式

观察者模式其实原理和监听器是一样的,使用的关键在搞清楚什么是观察者、什么是被观察者。

观察者(Observer)相当于事件监器。有个微博模型比较好理解,A用户关注B用户,则A是B的观察者,B是一个被观察者,一旦B发表任何言论,A便可以获得。

被观察者(Observable)相当于事件源和事件,执行事件源通知逻辑时,将会回调observer的回调方法update。

举个例子

为了方便,同样我直接使用jdk自带的Observer。

一个观察者

public class WatcherDemo implements Observer {

@Override

public void update(Observable o, Object arg) {

if(arg.toString().equals(“openWindows”)){

System.out.println(“已经打开窗口”);

}

}

}

被观察者

Observable 是jdk自带的被观察者,具体可以自行看源码和之前的监听器事件源类似。

主要方法有

addObserver() 添加观察者,与监听器模式类似

notifyObservers() 通知所有观察者

类Watched.java的实现描述:被观察者,相当于事件监听的事件源和事件对象。又理解为订阅的对象 主要职责:注册/撤销观察者(监听器),接收主题对象(事件对象)传递给观察者(监听器),具体由感兴趣的观察者(监听器)执行

/**

}

测试执行

public static void main(String[] args) {

Watched watched = new Watched();

WatcherDemo watcherDemo = new WatcherDemo();

watched.addObserver(watcherDemo);

watched.addObserver(new Observer(){

@Override

public void update(Observable o, Object arg) {

if(arg.toString().equals(“closeWindows”)){

System.out.println(“已经关闭窗口”);

}

}

});

//触发打开窗口事件,通知观察者

watched.notifyObservers(“openWindows”);

//触发关闭窗口事件,通知观察者

watched.notifyObservers(“closeWindows”);

控制台输出:

已经打开窗口

已经关闭窗口

总结

从整个实现和调用过程来看,观察者和监听器模式基本一样。

有兴趣的你可以基于这个模型,实现一个简单微博加关注和取消的功能。 说到底,就是事件驱动模型,将调用者和被调用者通过一个链表、回调函数来解耦掉,相互独立。

“你别来找我,有了我会找你”。

整个设计模式的初衷也就是要做到低耦合,低依赖。

再延伸下,消息中间件是什么一个模型? 将生产者+服务中心(事件源)和消费者(监听器)通过消息队列解耦掉. 消息这相当于具体的事件对象,只是存储在一个队列里(有消峰填谷的作用),服务中心回调消费者接口通过拉或取的模型响应。 想必基于这个模型,实现一个简单的消息中间件也是可以的。

还比如gava ListenableFuture,采用监听器模式就解决了future.get()一直阻塞等待返回结果的问题。

有兴趣的同学,可以再思考下观察者和责任链之间的关系, 我是这样看的。

同样会存在一个链表,被观察者会通知所有观察者,观察者自行处理,观察者之间互不影响。 而责任链,讲究的是击鼓传花,也就是每一个节点只需记录继任节点,由当前节点决定是否往下传。 常用于工作流,过滤器web filter。

java 设计模式之 观察者模式(Observer)

//Subject java

package youngmaster model Observer;

/**

* @author youngmaster

* @E mail:young * @version myEclipse

* @create time 上午 : :

*/

/**

* 察者模式属于行为型模式 其意图是定义对象间的一种一对多的依赖关系

* 当一个对象的状态发生改变时 所有依赖于它的对象都得到通知并被自动更新

* 在制作系统的过程中 将一个系统分割成一系列相互协作的类有一个常见的副作用

* 需要维护相关对象间的一致性 我们不希望为了维持一致性而使各类紧密耦合

* 因为这样降低了他们的可充用性 这一个模式的关键对象是目标(Subject)和观察者(Observer)

* 一个目标可以有任意数目的依赖它的观察者 一旦目标的状态发生改变 所有的观察者都得到通知

* 作为对这个通知的响应 每个观察者都将查询目标以使其状态与目标的状态同步 这种交互也称为发布 订阅模式

* 目标是通知的发布者 他发出通知时并不需要知道谁是他的观察者 可以有任意数据的观察者订阅并接收通知

*/

/**

* subject

*目标接口

*/

public interface Subject {

public void addObserver(Observer o);

public void deletObserver(Observer o);

public void notice();

}

//Observer java

package youngmaster model Observer;

/**

* @author youngmaster

* @E mail:young * @version myEclipse

* @create time 上午 : :

*/

/**

*观察者接口

*/

public interface Observer {

public void update();

}

//Teacher java

package youngmaster model Observer;

import java util Vector;

/**

* @author youngmaster

* @E mail:young * @version myEclipse

* @create time 上午 : :

*/

/**

*目标接口实现

*/

public class Teacher implements Subject {

private String phone;

@SuppressWarnings( unchecked )

private Vector students;

@SuppressWarnings( unchecked )

public Teacher() {

phone = ;

students = new Vector();

}

@SuppressWarnings( unchecked )

@Override

public void addObserver(Observer o) {

students add(o);

}

@Override

public void deletObserver(Observer o) {

students remove(o);

}

@Override

public void notice() {

for (int i = ; i students size(); i++) { ((Observer) students get(i)) update();

}

}

public void setPhone(String phone) {

this phone = phone;

notice();

}

public String getPhone() {

return phone;

}

}

//Student java

package youngmaster model Observer;

/**

* @author youngmaster

* @E mail:young * @version myEclipse

* @create time 上午 : :

*/

/**

*观察者接口实现

*/

public class Student implements Observer {

private String name;

private String phone;

private Teacher teacher;

public Student(String name Teacher teacher) { this name = name; this teacher = teacher;

}

public void show() {

System out println( Name: + name + \nTeacher s phone: + phone);

}

@Override

public void update() {

phone = teacher getPhone();

}

}

//Client java

package youngmaster model Observer;

import java util Vector;

/**

* @author youngmaster

* @E mail:young * @version myEclipse

* @create time 上午 : :

*/

/**

*测试类

*/

public class Client {

/**

* @param args

*/

@SuppressWarnings( unchecked )

public static void main(String[] args) {

Vector students = new Vector();

Teacher teacher = new Teacher();

for (int i = ; i ; i++) {

Student student = new Student( student + i teacher); students add(student); teacher addObserver(student);

}

teacher setPhone( );

for (int i = ; i ; i++)

((Student) students get(i)) show();

System out println( \n============================\n );

teacher setPhone( );

for (int i = ; i ; i++)

((Student) students get(i)) show();

}

lishixinzhi/Article/program/Java/gj/201311/27566

本文来自投稿,不代表【】观点,发布者:【

本文地址: ,如若转载,请注明出处!

举报投诉邮箱:253000106@qq.com

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2024年3月28日 08:38:25
下一篇 2024年3月28日 08:48:38

相关推荐

  • java中前面去0的简单介绍

    Java中怎么删除字符串开头的0 1、强制转换为整形,然后整形转字符串,开头的0就会去掉了。 2、在平时字符串处理中,会遇到这样的需求:去掉字符串前面的0例如:0000123456可以这样处理:String str = 0000123456;System.err.println(Integer.parseInt(str));输出结果:123456 前提是:字…

    2024年5月9日
    3700
  • 比较计数对数组排序java实现,java比较数组内容

    在java编程中如何对数组进行排序,并输出排序后的数组及原数组下标值 1、首先线性查找找到插入位置index,然后把index以后的数组元素都向后移动一个,再把新元素放到index处。 2、{ double[] arr = {5,3,5,8};//待排序的数组。double num ;//中间变量。 3、确定排序的时候把值进行了交换。确定输出的是数组的值,而…

    2024年5月9日
    3100
  • 关于java产生01随机数的信息

    java如何产生1-10随机数 1、利用random方法来生成随机数。在Java语言中生成随机数相对来说比较简单,因为有一个现成的方法可以使用。在Math类中,Java语言提供了一个叫做random的方法。通过这个方法可以让系统产生随机数。 2、用java的Random吧,Random r=new Random();int ran=r.nextInt(10)…

    2024年5月9日
    3100
  • java如何加载数据库,java引入数据库

    java如何连接SQLserver数据库? 方法:要向连接数据库,首先应该保证数据库服务打开 数据库服务打开之后就可以在环境中编写连接代码了。如图:连接数据库就是这两个步骤:1)加载驱动、2)创建连接。注意在导包是导入的java.sql下的。 加载JDBC驱动程序:在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机),这通过java.…

    2024年5月9日
    3300
  • 包含java读取16进制文件的词条

    急!!JAVA读取16进制文件每512个字符分一组,然后以字符串的形式存在数… 1、二进制文件,不知道数据格式,没人能帮你做。 2、String表示一串字符,它可以通过某些方法转换成一个数组,如char[], byte[],也可以用其他方法取出其中某个特定位置的字符,如charAt();与C里面不同,在Java中,通常String用的比较多,ch…

    2024年5月9日
    3600
  • java录音机,jc录音

    推荐几款三星支持JAVA的手机(1000以内) – 支持WAP 0浏览器及内置多款最新Java?游戏,把握每一个乐趣瞬间。- 支持录音机功能,以内存为限,单次录音时长最多可支持1小时,你的另一个记忆存储空间。 三星的手机,至少带java吧?你找找触屏机器就可以了。 支持WAP,GPRS,支持JAVA。字大声响的X138不但可以让老爸更清楚地看清屏…

    2024年5月9日
    3300
  • java调用webservice,java调用webservice没有返回结果

    JAVA怎样调用https类型的webservice 1、一步按照Axis生成本地访问客户端,完成正常的webservice调用的开发,这里的细节我就不再描述,重点说明和http不同的地方-证书的生成和 使用。 2、参考如下:使用JDK自带的工具创建密匙库和信任库。 3、Java调用WebService可以直接使用Apache提供的axis.jar自己编写代…

    2024年5月9日
    2800
  • java编程题全集及答案,java的编程题

    几道JAVA题目,求好心人给下答案,感激不尽 通过传感器采集的数据来说有视频流、音频流等,如果是底层数据处理的话参考java.io包,其它语言查阅官方文档。InputStream和OutputStream不可实例化因为是抽象的,这些可以参阅oracle提供的java产品文档。 字母、下划线、$组成。首位不能是数字,Java关键字不能当作Java标识符。25:…

    2024年5月9日
    3000
  • java如何保证守护进程,java保护权限

    如何设置java守护线程守护某一个线程 守护线程与普通线程在表现上没有什么区别,我们只需要通过Thread提供的方法来设定即可:void setDaemon(boolean )当参数为true时该线程为守护线程。守护线程的特点是,当进程中只剩下守护线程时,所有守护线程强制终止。 通过setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为…

    2024年5月9日
    3300
  • java的jdk1.8怎么安装路径的简单介绍

    如何安装jdk1.8和配置环境变量 第一条是jdk的路径,第二条是jre的路径。如果你不 如果不想安装在默认路径,请安装在其他路径。建议将这两个安装在同一个目录中。JAVA环境变量配置打开这台电脑,单击鼠标右键,选择属性打开系统设置,然后单击高级程序设置。 Win10配置jdk环境变量的方法:安装JDK 选择安装目录 安装过程中会出现两次 安装提示 。第一次…

    2024年5月9日
    3400

发表回复

登录后才能评论



关注微信