目录
一、观察者模式介绍
二、实例讲解
三、在Srping 中整合“观察者模式”
四、扩展性体现
五、Java自带观察者模式支持
引言
在java项目开发中,经常会把一些重要的数据放到数据库里,但如果这些数据在程序中会经常被使用到,就会频繁的查询数据库,导致数据库压力增加。常见的做法是把这些数据放到缓存里,比如redis等全局共享缓存,这样每次使用的使用时候不用查询数据库。
但如果使用redis作为缓存,每次读取都有网络开销。如果数据量不大的情况下,可以在程序启动时把这部分数据加载到jvm内存中(比如,public static的变量),每次使用这些数据的时候,直接从本地jvm内存中获取 性能方面势必会好很多,而且减少了外部依赖,系统稳定性方面也会好一些。但也有一个缺点:现在的程序都是多实例部署(多个jvm实例),每个jvm实例内存中都有一份数据,如果数据有修改,要同步更新每个jvm内存中数据 会有些困难。
常见的解决方案是引入配置管理系统(类似淘宝的diamond),多个jvm实例会向“配置管理系统”中的某个配置文件进行注册,当这些配置文件发送变化时,就会通知各个jvm实例拉取最新的配置。其实这个场景就是一个“观察者模式”的放大化使用,可以把“配置管理系统”中的某个配置文件看做是“主题”,多个jvm实例看做是“观察者”。当主题发生变化时,会通知观察者拉取最新的内容。
一、观察者介绍
定义了对象之间的一对多依赖关系,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。其中“一”的一方称为“主题对象”,“多”的一方称为“观察者对象”。采用此模式的优点:
1、“主题对象”和“观察者对象”之间相互隔离,彼此内部业务有修改,互不干扰。
2、“观察者对象”之间相互隔离,新增或删除“观察者对象”,不会影响其他“观察者对象”,具备良好的扩展性。
观察者模式中有4类角色:“抽象的主题”(接口 Subject)、“抽象的观察者”(接口 Observer)、“具体的主题”(实现 ConcreteSubject)、“具体的观察者”(实现 ConcreteObserver,可以有多个)。类图关系如下:
(来自互联网)
“推送型”和“拉取型”
根据数据的同步方式,“观察者模式”又分为“推送型”和“拉取型”:
1、“推送型”:观察者的update方法参数中包含所有的业务数据,“具体的观察者”根据自己的业务获取自己需要的数据。优点是数据隔离性好,不会对主题对象造成破坏;缺点是 如果参数过多看起来会比较臃肿,有些观察者不需要的数据也会被推送。
2、“拉取型” :在“具体的主题对象”实现中提供一些获取数据的public方法,在update方法中直接调用这些方法获取自己需要的数据。优点是 自己需要什么数据自己去拿,缺点是 如果“具体的主题对象”实现设计不当,容易暴露一些私有的数据和方法。
当然也不是绝对的只有这两种,有些主题里的数据量很少,有可能只需要一个状态变化来触发“具体的观察者”对象的update方法做一些业务操作即可,不需要获取主题中的数据。下面实例讲解中的案例,就属于这种情况。
二、实例讲解
“观察者模式”最简单明了的例子是《Head First》设计模式一书中“气象观察站”的例子,可以网上搜索了解下,这里不细讲。实际项目中,很难遇到与此场景完全匹配的例子,但整体思路是一致的,根据实际情况调整即可。
这里引用最近项目中的一个实际例子进行讲解,本实例完整代码详见github:https://github.com/gantianxing/observer-test.git。
回到文章开头的场景,最近一个项目中需要把一些常用的数据放到jvm内存,引入配置管理系统进行数据同步。由于配置数据项较多,本项目中没有把配置数据直接以配置文件的形式放到“配置管理系统”,而是把具体的数据放到数据库,再把每项数据是否修改做出开关配置放到“配置管理系统”。数据同步流程为:
1、程序启动时,从数据库读取数据放到jvm内存。
2、当数据库中某条数据发生变化时:“数据管理后台”发布新数据到数据库,同时修改“配置管理系统”中的配置开关。这里有一个“配置开关”列表,对应数据库中的多个数据项。
3、各个jvm实例获取到变化的配置开关,触发再次读取数据库中的新数据 同步到jvm内存。
这里的“配置开关”列表即为“具体的主题对象”,“多个数据项”对应的多个同步类即为:“具体的观察者”。上述流程关系如下:
本次讲解忽略“数据管理后台”相关操作,主要关注 当开关列表发生变化时,触发对应的“同步类”从数据库拉取最新数据。该部分流程,采用“观察者模式”实现:
1、抽象的主题(ConfigSubject):
public interface ConfigSubject { void registerObserver(String key, ConfigRloadService o);//注册观察者 void removeObserver(String key);//删除观察者 void notifyObervers(String conf);//通知观察者 }
2、抽象的观察者(ConfigRloadService):
public interface ConfigRloadService { void reload(); }
3、具体的主题(ConfigSubjectImpl):
@Component("subject") public class ConfigSubjectImpl implements ConfigSubject { //观察者列表 private Map<String,ConfigRloadService> observers = new HashMap(); @Override public void registerObserver(String key,ConfigRloadService o) { observers.put(key, o); } @Override public void removeObserver(String key) { observers.remove(key); } /** * 通知对应的观察者 * @param conf */ @Override public void notifyObervers(String conf) { if(conf!=null && conf.length()>0){ String [] configAray=conf.split("\\r\\n"); //回车换行做为配置分割,一行一个配置项 for(String one:configAray){ String [] oneArray = one.split("="); if(oneArray.length !=2){ continue; } String key = oneArray[0]; String value = oneArray[1]; int iv_new = Integer.parseInt(value); int iv_old = SwitchEnum.valueOf(key).getValue(); if(iv_new != iv_old){//如果配置开关不相等,说明配置内容已经更改,需要重新reload数据 ConfigRloadService service = observers.get(key); if(service != null){ service.reload(); } SwitchEnum.valueOf(key).setValue(iv_new); } } } } }
4、具体的观察者(两个:ActPcRloadServiceImpl、ActMobRloadServiceImpl)
@Component public class ActPcRloadServiceImpl implements ConfigRloadService { @Resource private ConfigSubject subject; public ActPcRloadServiceImpl(ConfigSubject subject){ this.subject = subject; subject.registerObserver(SwitchEnum.actPcConf.name(),this); } @Override public void reload() { //查询数据库加载最新的配置内容到jvm内存,代码逻辑省略 System.out.println("pc版活动配置reload"); } }
@Component public class ActMobRloadServiceImpl implements ConfigRloadService { @Resource private ConfigSubject subject; public ActMobRloadServiceImpl(ConfigSubject subject){ this.subject = subject; subject.registerObserver(SwitchEnum.actMobConf.name(),this); } @Override public void reload() { //查询数据库加载最新的配置内容到jvm内存,代码逻辑省略 System.out.println("移动端活动配置reload"); } }
辅助常量枚举类,对应多个配置开关,这里只有两个,分别对应上述两个“具体的观察者”:
public enum SwitchEnum { actMobConf(0), actPcConf(0); private int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; } SwitchEnum(int value){ this.value = value; } }
测试类:
public class Main { public static void main(String[] args) { String conf = "actMobConf=1\r\nactPcConf=0";//模拟开关配置发生改变 //step1 初始化主题和观察者,在spring中可以用spring ioc自动注入代替 ConfigSubject subject = new ConfigSubjectImpl();//创建主题 ConfigRloadService actMobRloadServiceImpl = new ActMobRloadServiceImpl(subject);//创建观察者 ConfigRloadService actPcRloadServiceImpl = new ActPcRloadServiceImpl(subject);//创建观察者 //step2 模拟开关配置改变,触发观察者reload方法 subject.notifyObervers(conf); } }
其中String conf = "actMobConf=1\r\nactPcConf=0"; 这里有两个配置开关,分别对应SwitchEnum中的两个配置开关,修改String conf中配置值与SwitchEnum中默认的默认值不同,即可触发指定的“同步类”从数据库拉取新数据。这里把actMobConf改为了1,按逻辑会触发ActMobRloadServiceImpl的reload方法执行。运行上述main方法,查看结果:
移动端活动配置reload
说明测试结果与预期相符。
三、在Srping 中整合“观察者模式”
上一节使用的main方法来实例化bean,在实战中我们一般都是使用的Spring ioc容器,使用起来更方便,直接添加@Component注解即可。这里就不细讲,可以直在tomcat中运行github: https://github.com/gantianxing/observer-test.git中的代码,启动后访问http://localhost
,修改配置项 观察控制台日志看效果。
观察控制台日志:
四、扩展性体现
假设现在业务新增一项数据 需要同步到jvm内存,此时只需要三步操作:
1、新增一个同步类XXXRloadServiceImpl(实现ConfigRloadService接口),实现自己的数据加载方法reload。
2、在枚举类SwitchEnum中新增一个配置项xxxPcConf(0)。
3、在配置管理系统的 配置开关列表中,新增一个开关xxxPcConf=1,即可实现数据同步。
可见整个扩展过程,对原有逻辑没有任何影响。
五、Java自带观察者模式支持
由于观察者模式是一个常见的设计模式,Java api提供的两个工具类对“观察者模式”进行支持,java.util包中的Observable类 和Observer接口:
Observable类对应“抽象的主题”,只是这里不是接口,“具体的主题”需要继承该类。
Observer接口对应“抽象的观察者”,“具体的观察者”实现该接口即可。
有些简单的场景可以直接使用,但有一定局限,比如:
1、Observable主题类中的“观察者”列表,是用的Vector存储。上述示例中需要用Map就无能为力。
private Vector<Observer> obs = new Vector();
2、notifyObservers方法,遍历所有的“观察者”,执行其update方法。上述示例中如果需要过滤部分“观察者”执行其update方法,也无能为力。
其实观察者模式实现起来也比较简单,根据自己的业务自己实现也不难,java自带的可以作为参考即可,尤其是的Observable类的设计思想。
转载请注明出处:
相关推荐
行为型模式则关注对象之间的交互和职责分配,如策略模式(Strategy)、观察者模式(Observer)和责任链模式(Chain of Responsibility)等,它们有助于提高代码的灵活性和可扩展性。 在C#中,设计模式的实现往往...
在《深入浅出设计模式》的学习笔记中,我们可以看到几种关键的设计模式及其应用,包括策略模式、观察者模式、装饰者模式、工厂模式和抽象工厂模式。 1. **策略模式**: - 策略模式的核心在于将算法族封装在独立的...
在代码注释中,提到将observer改造成构造函数的方式,即让village2也采用同样的发布-订阅机制,这表示将观察者模式进行泛化,使它可以适用于多个不同的“村子”对象。这种改造能够使系统更加灵活,扩展性更好。 ...
接着,书中会逐一解析创建型模式(如工厂方法、抽象工厂、单例等)、结构型模式(如适配器、装饰器、代理等)和行为型模式(如策略、观察者、责任链等)。每种模式都会结合C#代码实例进行详细阐述,帮助读者更好地...
观察者模式允许多个对象监听这些事件并做出响应,提高了代码的灵活性和可扩展性。 接着,"策略模式"可能用于处理不同的绘图工具。例如,画笔、橡皮擦、选择工具等都可以看作是不同的策略,根据用户的选择动态地改变...
11. 观察者模式:定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。这种模式广泛应用于事件驱动编程和发布订阅系统。 12. 状态模式:允许对象在其内部状态...
观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。在C++中,观察者模式常用于事件驱动编程,如GUI界面中的事件处理。...
3. **观察者模式(Observer)**:定义对象间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。 4. **命令模式(Command)**:将一个请求封装为一个对象,从而使你可用...
6. **观察者模式**(发布/订阅):定义对象间的一种一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。 7. **装饰器模式**:动态地给一个对象添加一些额外的职责,提供了一...
例如,在现有代码中引入观察者模式时,可能需要先进行一系列的重构操作,比如提取接口、调整类结构等,才能顺利地将模式融入现有设计中。 ### 实践中的应用 #### 使用模式改善现有设计 《重构与模式》一书强调了...
5. **观察者模式**:定义对象间的一种一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。这种模式在事件驱动编程中非常常见。 6. **装饰器模式**:动态地给一个对象添加一些...
- Dubbo在实现中广泛应用了工厂模式、代理模式、观察者模式等设计模式。 15. **Dubbo的运维管理** - 支持telnet命令进行服务管理和诊断 - 支持服务降级,当服务不可用时,可以降级为本地缓存或返回默认值 - ...
- **JS常用设计模式**:包括工厂模式、单例模式、观察者模式等。 - **应用场景**:改善代码的可读性、可维护性和扩展性。 ### 四、疑难问题解决方案 #### 常见问题分析 - **性能优化**:针对响应时间过长、内存...
- **性能评估**:通过大量并发发送和接收测试,评估MQ的吞吐量、延迟和可扩展性。 - **故障模拟**:模拟网络故障、服务器故障等异常情况,测试MQ的容错和恢复机制。 - **日志分析**:提供日志查看和分析功能,...
6. **设计模式**:设计模式是软件设计中的一些常见问题的解决方案,如单例模式、工厂模式、观察者模式等。理解和应用设计模式能提高代码质量,使程序更易于维护和扩展。 7. **程序设计原则**:书中可能会介绍一些...
ViewModel帮助在Activity或Fragment重建时保持数据,而LiveData则提供了一个观察者模式,使得UI可以实时响应后台数据的变化。 3. **Retrofit库**:Retrofit是由Square公司开发的一个HTTP客户端库,它允许开发者以...
报告指出,随着社会的发展和消费观念的升级,人们对美好居住的定义已经从单纯的物质满足扩展到精神享受。 报告中提到,中国家庭户均人口规模在减少,家庭模式正向小型化转变,这直接影响了人们对居住空间的需求。...
- 可持续发展:考虑未来的业务扩展需求,确保系统的可扩展性。 - 安全可靠:保障数据安全和个人隐私。 - **设计依据**: - 国家相关政策法规,如《中国制造2025》等。 - 行业标准和技术规范,确保系统符合行业...
4. **观察者模式(Observer Pattern)**: - 定义:定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。 - 应用场景:适用于实时更新显示数据的应用程序,...