>>分享Java编程技术,对《Java面向对象编程》等书籍提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 17194 个阅读者 刷新本主题
 * 贴子主题:  使用策略模式优化代码实践,如何让项目快速起飞 回复文章 点赞(0)  收藏  
作者:flybird    发表时间:2020-11-24 07:34:36     消息  查看  搜索  好友  邮件  复制  引用

一、背景

之前接手了一个 springboot 项目。在我负责的模块中,有一块用户注册的功能,但是比较特别的是这个注册并不是重新注册,而是从以前的旧系统的数据库中同步旧数据到新系统的数据库中。由于这些用户角色来自于不同的系统,所以我需要在注册的时候先判断类型(这个类型由一个专门的枚举类提供),再去调用已经写好的同步方法同步数据。

     伪代码大概是这样的:    

public void register(String type, String userId, String projectId, String declareId){
    // 判断用户类型
    if (UserSynchronizeTyeEnum.A.type.equals(type)) {
        // 同步A类型的数据
    } else if (UserSynchronizeTyeEnum.A.type.equals(type)) {
        // 同步B类型的数据
    } else {
        throw new RuntimeException("不存在的用户类型");
    }
    ... ...
}

  由于用户的类型比较多,所以当我接手的时候已经有8个 if-esle 了,由于这个项目会逐步的跟其他平台对接,要同步的用户类型会越来越多,而且也不能排除什么时候不新增,反而要取消一部分类型的同步情况。

     就这个情况来说,一方面每一次新增类型都会让 if-else 串越来越长,取消一些类型的同步还要直接删除 if-else 里的对应代码;另一方面,这个业务的需求相对稳定,同步方法会不一样,但是一定会根据类型来判断。出于以上考虑,我决定趁现在牵扯范围不大的时候重构一下。

二、思路

1.抽取策略接口和策略类

首先,由于每种用户类型的同步方法是由各模块自己提供的,其实已经抽出了策略,只是没有实现一个统一的策略接口。

     但是我在这一步遇上了问题:
  • 各模块的同步方法的名称不全部一样;
  • 由于年代久远,旧代码是不允许改的。
代码不让改,就没法通过为旧实现类新增接口实现多态,方法名不一样,那么反射这条路子也走不通。我想到了装饰器,为每个实现类新增一个装饰器类,注册的时候通过装饰器去调用同步方法,但是这样缺点很明显,会引入一个装饰器接口+n多个装饰器类,为了优化这一个方法,反而要引入十几个类,实在是脱裤子放屁。

     但是好在天无绝人之路,他们并不是完全没有相同点:
  • 虽然参数名不一样,但是 每个同步方法都需要的参数数量和类型都是一样的;
  • 他们都返回一个布尔值
这让我想起了 JDK8 的函数式接口,将策略接口改造为函数式接口,由于同步方法的参数和返回值类型都是一样的,就可以直接以 Lambda 表达式的形式将各个模块的同步方法放进去,这样就不需要改动模块的代码了。

     新增的接口如下:    

@FunctionalInterface
public interface IUserSynchronizeSerivice {

    /**
     * 同步方法
     */

    public boolean sync(String userId, String projectId, String declareId);

}

2.策略池的实现

接着,为了实现原本 if-else 的逻辑,我需要一个策略池,能够建立起一个用户类型跟对应的同步策略的映射关系,一开始,我打算直接写在 register()方法所在的类中加入以下代码:    

@Autowired
private AUserService aUserService;
@Autowired
private BUserService bUserService;

private static final Map<String, UserSynchronizeTyeEnum.IUserSynchronizeService> synchronizeServiceStrategy = new HashMap<>();
@PostConstruct
private void strategyInit(){
    // spring容器启动后将策略装入策略池
    synchronizeServiceStrategy.put(UserSynchronizeTyeEnum.A.type, aUserService::synchronization);
    synchronizeServiceStrategy.put(UserSynchronizeTyeEnum.B.type, bUserService::sync);
}

  但是这样在添加新的用户类型时,需要先去枚举类添加新枚举,然后再回到register()所在的类为策略池添加策略,这个两个逻辑上相连的过程被分散到了两个地方,而且仍然要修改 register()所在类的代码。所以决定不用上述的代码,而是去对枚举类下手。

     原本的枚举类是这样的:    

/**
* 老系统用户注册,用户类型与同步方法的枚举类
*/

public enum UserSynchronizeTyeEnum {
    /**
     * 类型A的用户
     */

    A("a"),

    /**
     * 类型B的用户
     */

    B("b");

    /**
     * 用户类型
     */

    private final String type;

    UserSynchronizeTyeEnum(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }
}

  为了保证逻辑能够集中,我决定将添加策略这一过程一起放到到枚举类里,在添加枚举的时候就把策略一起放进去:
  注:下文的 SpringUtils 实现了 BeanFactoryPostProcessor 接口,是一个用于从 ConfigurableListableBeanFactory 获取对象的工具类。
/**
* 老系统用户注册,用户类型与同步方法的枚举类
* 添加新类型时,需要将模块对应的同步方法一并放入
*/

public enum UserSynchronizeTyeEnum {

    /**
     * 类型A的用户
     */

    A("a", (userId, projectId, declareId) -> {
        return SpringUtils.getBean(AUserService.class).synchronization(userId, projectId, declareId);
    }),

    /**
     * 类型B的用户
     */

    B("b", (userId, projectId, declareId) -> {
        return SpringUtils.getBean(BUserService.class).sync(userId, projectId, declareId);
    });

    /**
     * 用户类型
     */

    private final String type;

    /**
     * 同步方法
     */

    private final IUserSynchronizeService synchronizeService;

    UserSynchronizeTyeEnum(String type, IUserSynchronizeService synchronizeService) {
        this.type = type;
        this.synchronizeService = synchronizeService;
    }
}

  由于由于枚举类已经相当于之前策略池的 Map 集合了,所以我们直接在里面添加一个getSynchronizeService()方法,用于直接获取同步方法:    

/**
* 根据枚举值获取对应同步方法
*/

public Optional<IUserSynchronizeService> getSynchronizeService(String type) {
    for (UserSynchronizeTyeEnum tyeEnum : UserSynchronizeTyeEnum.values()) {
        if (tyeEnum.type.equals(type)) {
            return Optional.of(tyeEnum.synchronizeService);
        }
    }
    return Optional.empty();
}

  到目前为止,策略池已经基本完成了,但是我们不难发现,现在为策略接口添加实现的地方也变成了枚举类中,策略接口 IUserSynchronizeService一般也不会被用在其他地方,因此不妨 把策略接口也一并引入枚举类中,让他成为一个枚举类的内部接口

     现在,枚举类是这样的:

     [url=links.jianshu.com/go?to=http%3A%2F%2Fimg.xiajibagao.top%2Fimage-20201121161017904.png][/url]

           点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小



              策略模式的枚举类

     枚举类堆外只暴露根据类型获取方法的IUserSynchronizeService()方法,以及 A 和 B 两个枚举。

     完整的UserSynchronizeTyeEnum枚举类代码如下:    

/**
* 老系统用户注册,用户类型与同步方法的枚举类
* 添加新类型时,需要将模块对应的同步方法一并放入。待用户注册时,会遍历枚举对象并根据类型获取对应的同步方法执行。
*/

public enum UserSynchronizeTyeEnum {

    /**
     * 类型A的用户
     */

    A("a", (userId, projectId, declareId) -> {
        return SpringUtils.getBean(AUserService.class).synchronization(userId, projectId, declareId);
    }),

    /**
     * 类型B的用户
     */

    B("b", (userId, projectId, declareId) -> {
        return SpringUtils.getBean(BUserService.class).sync(userId, projectId, declareId);
    });

    /**
     * 用户类型
     */

    public String type;

    /**
     * 同步方法
     */

    public IUserSynchronizeService synchronizeService;

    UserSynchronizeTyeEnum(String type, IUserSynchronizeService synchronizeService) {
        this.type = type;
        this.synchronizeService = synchronizeService;
    }

    /**
     * 根据枚举值获取对应同步方法
     */

    public static Optional<IUserSynchronizeService> getSynchronizeService(String type) {
        for (UserSynchronizeTyeEnum tyeEnum : UserSynchronizeTyeEnum.values()) {
            if (tyeEnum.type.equals(type)) {
                return Optional.of(tyeEnum.synchronizeService);
            }
        }
        return Optional.empty();
    }

    /**
     * 同步方法需要符合函数式接口
     */

    @FunctionalInterface
    public interface IUserSynchronizeService {
        boolean sync(List<Map<String, Object>> dateList, String userId, String projectId, String declareId);
    }
}

三、使用

现在,改造完毕,可以开始使用了,对于原先的 register()方法,现在改为:    

public void register(String type, String userId, String projectId, String declareId){
    // 获取同步方法,没有就抛异常
    UserSynchronizeTyeEnum.IUserSynchronizeService synchronizeService = UserSynchronizeTyeEnum.getSynchronizeService(type)
        .orElseThrow(() -> new RuntimeException("类型不存在"));
    // 同步用户数据
    synchronizeService.sync(userId, projectId, declareId);
}

  当我们需要再添加一个 C 类用户的同步注册的时候,只需要前往枚举类添加:    

/**
* 类型C的用户
*/

C("c", (userId, projectId, declareId) -> {
    return SpringUtils.getBean(CUserService.class).sync(userId, projectId, declareId);
});

  即可,register()方法就不需要再做修改了。


----------------------------
原文链接:https://www.jianshu.com/p/9e9530650c18

程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 flybird 在 2020-11-25 12:06:35 重新编辑]
  Java面向对象编程-->多线程(上)
  JavaWeb开发-->JavaWeb应用入门(Ⅱ)
  JSP与Hibernate开发-->持久化层的映射类型
  Java网络编程-->对象的序列化与反序列化
  精通Spring-->Vue组件开发高级技术
  Vue3开发-->Vue简介
  CRMEB_Java新零售社交电商系统
  《漫画Java编程》勘误及建议
  面试官:NIO的优化实现原理了解吗?图文结合教你如何正确避坑
  Java设计模式:接口隔离原则和迪米特法则详解
  观察者模式和发布订阅模式的区别
  使用 RocketMQ 事务消息,实现分布事务
  最实用的10个重构小技巧排行榜,你都用过哪些?
  java.util.logging.Logger使用详解
  redis持久化问题处理
  5个非常有挑战性的Java面试题
  Java入门实用代码: List 列表中元素的替换
  Java入门实用代码:修改文件最后的修改日期
  Java入门实用代码: 字符串格式化
  史上最全正则表达式合集(马上收藏)
  【Java 并发笔记】CountDownLatch 相关整理
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。