扩展性改造--观察者模式

阅读更多

目录

 

一、观察者模式介绍

二、实例讲解

三、在Srping 中整合“观察者模式”

四、扩展性体现

五、Java自带观察者模式支持

 

引言

 

在java项目开发中,经常会把一些重要的数据放到数据库里,但如果这些数据在程序中会经常被使用到,就会频繁的查询数据库,导致数据库压力增加。常见的做法是把这些数据放到缓存里,比如redis等全局共享缓存,这样每次使用的使用时候不用查询数据库。

 

但如果使用redis作为缓存,每次读取都有网络开销。如果数据量不大的情况下,可以在程序启动时把这部分数据加载到jvm内存中(比如,public static的变量),每次使用这些数据的时候,直接从本地jvm内存中获取 性能方面势必会好很多,而且减少了外部依赖,系统稳定性方面也会好一些。但也有一个缺点:现在的程序都是多实例部署(多个jvm实例),每个jvm实例内存中都有一份数据,如果数据有修改,要同步更新每个jvm内存中数据 会有些困难。

 

常见的解决方案是引入配置管理系统(类似淘宝的diamond),多个jvm实例会向“配置管理系统”中的某个配置文件进行注册,当这些配置文件发送变化时,就会通知各个jvm实例拉取最新的配置。其实这个场景就是一个“观察者模式”的放大化使用,可以把“配置管理系统”中的某个配置文件看做是“主题”,多个jvm实例看做是“观察者”。当主题发生变化时,会通知观察者拉取最新的内容。


扩展性改造--观察者模式_第1张图片
 

 

一、观察者介绍

 

定义了对象之间的一对多依赖关系,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。其中“一”的一方称为“主题对象”,“多”的一方称为“观察者对象”。采用此模式的优点:

1、“主题对象”和“观察者对象”之间相互隔离,彼此内部业务有修改,互不干扰。

2、“观察者对象”之间相互隔离,新增或删除“观察者对象”,不会影响其他“观察者对象”,具备良好的扩展性。

 

观察者模式中有4类角色:“抽象的主题”(接口 Subject)、“抽象的观察者”(接口 Observer)、“具体的主题”(实现 ConcreteSubject)、“具体的观察者”(实现 ConcreteObserver,可以有多个)。类图关系如下:


扩展性改造--观察者模式_第2张图片
 

                             (来自互联网)

 

“推送型”和“拉取型

 

根据数据的同步方式,“观察者模式”又分为“推送型”和“拉取型”:

1、“推送型”:观察者的update方法参数中包含所有的业务数据,“具体的观察者”根据自己的业务获取自己需要的数据。优点是数据隔离性好,不会对主题对象造成破坏;缺点是 如果参数过多看起来会比较臃肿,有些观察者不需要的数据也会被推送。

 

2、“拉取型” :在“具体的主题对象”实现中提供一些获取数据的public方法,在update方法中直接调用这些方法获取自己需要的数据。优点是 自己需要什么数据自己去拿,缺点是 如果“具体的主题对象”实现设计不当,容易暴露一些私有的数据和方法。

 

当然也不是绝对的只有这两种,有些主题里的数据量很少,有可能只需要一个状态变化来触发“具体的观察者”对象的update方法做一些业务操作即可,不需要获取主题中的数据。下面实例讲解中的案例,就属于这种情况。

 

二、实例讲解

 

“观察者模式”最简单明了的例子是《Head First》设计模式一书中“气象观察站”的例子,可以网上搜索了解下,这里不细讲。实际项目中,很难遇到与此场景完全匹配的例子,但整体思路是一致的,根据实际情况调整即可。

 

这里引用最近项目中的一个实际例子进行讲解,本实例完整代码详见github:https://github.com/gantianxing/observer-test.git。

 

回到文章开头的场景,最近一个项目中需要把一些常用的数据放到jvm内存,引入配置管理系统进行数据同步。由于配置数据项较多,本项目中没有把配置数据直接以配置文件的形式放到“配置管理系统”,而是把具体的数据放到数据库,再把每项数据是否修改做出开关配置放到“配置管理系统”。数据同步流程为:

1、程序启动时,从数据库读取数据放到jvm内存。

2、当数据库中某条数据发生变化时:“数据管理后台”发布新数据到数据库,同时修改“配置管理系统”中的配置开关。这里有一个“配置开关”列表,对应数据库中的多个数据项。

3、各个jvm实例获取到变化的配置开关,触发再次读取数据库中的新数据 同步到jvm内存。

这里的“配置开关”列表即为“具体的主题对象”,“多个数据项”对应的多个同步类即为:“具体的观察者”。上述流程关系如下:

 
扩展性改造--观察者模式_第3张图片

本次讲解忽略“数据管理后台”相关操作,主要关注 当开关列表发生变化时,触发对应的“同步类”从数据库拉取最新数据。该部分流程,采用“观察者模式”实现:

 

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 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

,修改配置项 观察控制台日志看效果。


扩展性改造--观察者模式_第4张图片
 

 

观察控制台日志:


扩展性改造--观察者模式_第5张图片
 

 

四、扩展性体现

 

假设现在业务新增一项数据 需要同步到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 obs = new Vector();

 

2、notifyObservers方法,遍历所有的“观察者”,执行其update方法。上述示例中如果需要过滤部分“观察者”执行其update方法,也无能为力。

 

其实观察者模式实现起来也比较简单,根据自己的业务自己实现也不难,java自带的可以作为参考即可,尤其是的Observable类的设计思想。

 

转载请注明出处:

http://moon-walker.iteye.com/blog/2395614

  • 扩展性改造--观察者模式_第6张图片
  • 大小: 21.8 KB
  • 扩展性改造--观察者模式_第7张图片
  • 大小: 92.7 KB
  • 扩展性改造--观察者模式_第8张图片
  • 大小: 61.2 KB
  • 扩展性改造--观察者模式_第9张图片
  • 大小: 7.3 KB
  • 扩展性改造--观察者模式_第10张图片
  • 大小: 28.3 KB
  • 查看图片附件

你可能感兴趣的:(观察者模式)