观察者(订阅)模式
创始人
2024-03-02 12:19:12
0

文章目录

    • 思考观察者模式
      • 1.观察者模式的本质
      • 2.何时选用观察者模式
      • 3.优缺点
      • 4.实现
        • 手写观察者模式
        • JDK观察者模式

思考观察者模式

观察者模式是典型的发布订阅模式,当一个东西有变化了,就通知所有订阅他的人

1.观察者模式的本质

观察者模式的本质:触发联动。

当修改目标对象的状态的时候,就会触发相应的通知,然后会循环调用所有注册的观察者对象的相应方法,其实就相当于联动调用这些观察者的方法。

而且这个联动还是动态的,可以通过注册和取消注册来控制观察者,因而可以在程序运行期间,通过动态地控制观察者,来变相地实现添加和删除某些功能处理,这些功能就是观察者在update的时候执行的功能。

同时目标对象和观察者对象的解耦,又保证了无论观察者发生怎样的变化,目标对象总是能够正确地联动过来。

理解这个本质对我们非常有用,对于我们识别和使用观察者模式有非常重要的意义,尤其是在变形使用的时候。万变不离其宗。

2.何时选用观察者模式

建议在以下情况中选用观察者模式。

  • 当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化,那么就可以选用观察者模式,将这两者封装成观察者和目标对象,当目标对象变化的时候,依赖于它的观察者对象也会发生相应的变化。这样就把抽象模型的这两个方面分离开了,使得它们可以独立地改变和复用。

  • 如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变,这种情况可以选用观察者模式,被更改的那一个对象很明显就相当于是目标对象,而需要连带修改的多个其他对象,就作为多个观察者对象了。

  • 当一个对象必须通知其他的对象,但是你又希望这个对象和其他被它通知的对象是松散耦合的。也就是说这个对象其实不想知道具体被通知的对象。这种情况可以选用观察者模式,这个对象就相当于是目标对象,而被它通知的对象就是观察者对象了。

3.优缺点

观察者模式具有以下优点

  • 观察者模式实现了观察者和目标之间的抽象耦合
    原本目标对象在状态发生改变的时候,需要直接调用所有的观察者对象,但是抽象出观察者接口以后,目标和观察者就只是在抽象层面上耦合了,也就是说目标只是知道观察者接口,并不知道具体的观察者的类,从而实现目标类和具体的观察者类之间解耦。

  • 观察者模式实现了动态联动
    所谓联动,就是做一个操作会引起其他相关的操作。由于观察者模式对观察者注册实行管理,那就可以在运行期间,通过动态地控制注册的观察者,来控制某个动作的联动范围,从而实现动态联动。

  • 观察者模式支持广播通信
    由于目标发送通知给观察者是面向所有注册的观察者,所以每次目标通知的信息就要对所有注册的观察者进行广播。当然,也可以通过在目标上添加新的功能来限制广播的范围。
    在广播通信的时候要注意一个问题,就是相互广播造成死循环的问题。比如A和B两个对象互为观察者和目标对象,A对象发生状态变化,然后A来广播信息,B对象接收到通知后,在处理过程中,使得B对象的状态也发生了改变,然后B来广播信息,然后A对象接到通知后,又触发广播信息……,如此A引起B变化,B又引起A变化,从而一直相互广播信息,就造成死循环。

观察者模式的缺点是:

  • 可能会引起无谓的操作
    由于观察者模式每次都是广播通信,不管观察者需不需要,每个观察者都会被调用update方法,如果观察者不需要执行相应处理,那么这次操作就浪费了。其实浪费了还好,最怕引起误更新,那就麻烦了,比如,本应该在执行这次状态更新前把某个观察者删除掉,这样通知的时候就没有这个观察者了,但是现在忘掉了,那么就会引起误操作。

4.实现

在这里插入图片描述

模拟:手机降价通知用户

手写观察者模式

1.目标类

/*** @description:通用目标接口,也可为抽象类*/
public class Subject {/*** 维护一个观察者列表*/private List list=new ArrayList<>();/*** 注册观察者* @param observer*/public void attach(Observer observer){list.add(observer);}/*** 移除观察者* @param observer*/public void detach(Observer observer){list.remove(observer);}/*** 通知观察者*/public void notifyObservers(){list.stream().forEach(obj->obj.update(this));}
}/*** @description:手机目标对象*/
@Getter
@ToString
public class PhoneSubject extends Subject{/*** 手机价格*/private double price;public void setPrice(double price) {this.price = price;//价格小于150为降价if (price<150){//通知用户降价notifyObservers();}}
}

2.观察者类

/*** @description:观察者接口*/
public interface Observer {/*** 被通知的方法* @param subject 目标对象*/void update(Subject subject);
}/*** @description:用户(观察者)*/
@Data
public class UserObserver implements Observer{/*** 用户名*/private String name;@Overridepublic void update(Subject subject) {System.out.println(this.name +"收到了降价通知,价格为"+((PhoneSubject)subject).getPrice());}
}

3.测试类

/*** @description:测试类*/
public class Client {public static void main(String[] args) {UserObserver userObserver1 = new UserObserver();userObserver1.setName("张三");UserObserver userObserver2 = new UserObserver();userObserver2.setName("李四");PhoneSubject subject=new PhoneSubject();//初始价格为150subject.setPrice(150);//绑定观察者subject.attach(userObserver1);subject.attach(userObserver2);//涨价不通知subject.setPrice(250);//降价才通知用户,第一次降价subject.setPrice(100);//降价才通知用户,第二次降价subject.setPrice(88);}
}

4.效果
在这里插入图片描述

JDK观察者模式

1.目标类继承Observable接口即可

/*** @description:手机目标对象*/
@Getter
@ToString
public class PhoneSubject2 extends Observable {/*** 手机价格*/private double price;public void setPrice(double price) {this.price = price;//价格小于150为降价if (price<150){//通知用户降价notifyObservers();}}
}

里面维护了观察者对象,以及注册、移除观察者,通知观察者等
在这里插入图片描述
2.观察者类实现Observer接口即可

/*** @description:用户1*/
@Data
public class UserObserver2 implements Observer {/*** 用户名*/private String name;@Overridepublic void update(Observable o, Object arg) {if (o instanceof PhoneSubject2){System.out.println(this.name +"收到了降价通知,价格为"+((PhoneSubject2)o).getPrice());}}
}

和手写的一模一样
在这里插入图片描述

3.测试类

/*** @author conggq* @description:测试类* @createTime 2022/11/29 17:27*/
public class Client {public static void main(String[] args) {UserObserver userObserver1 = new UserObserver();userObserver1.setName("张三");UserObserver userObserver2 = new UserObserver();userObserver2.setName("李四");PhoneSubject2 subject2=new PhoneSubject2();//初始价格为150subject2.setPrice(150);//涨价不通知subject.setPrice(250);//降价才通知用户,第一次降价subject.setPrice(100);//降价才通知用户,第二次降价subject.setPrice(88);}
}

结果一样

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...