基于多种设计模式重构代码(工厂、模板、策略)

基于多种设计模式重构代码

现状

  系统目前支持三种业务流程,业务A, 业务B,业务C,每个流程有相同的业务逻辑,也包含很多的特性化业务。

 由于之前业务流程的开发是快速迭代的,而且迭代了很多次,开发同学们应该可以理解,过程中免不了面向过程、CV大法...

 现在无论是业务发展,还是我们产研团队对于业务的理解,都趋于稳定,是时候该还债了,重构代码,我辈义不容辞!

恰好,新需求来了,需要在原有业务B流程中增加一个业务环节,申请支付:业务发起时扣减对应客户的资产余额。

业务流程A抽象后伪码如下:

java 复制代码
public void applyPay(Request request) {
        //数据查询
        query();
        //数据校验
        doCheck(request);
        //业务逻辑处理
        if (request.getFlag()) {
            //资产校验
            //扣减资产
            //生成审批流数据
            //推送钉钉
            processA();
        } else {
            //资产校验
            //扣减资产
            //生成审批流数据
            //推送钉钉
            processB();
        }
        //数据更新
        update();
    }

更巧的是之前业务A中做过类似的业务,而且需求评估的时候产品信誓旦旦的说业务B、C中肯定不会有这个业务。

可以预见的是业务C中将来也会有这个业务。

Don not Repeat yourself,这个逻辑公共化处理是必要的。


模板模式

对于上述业务流程,很容易想到模板模式对代码进行通用性抽象优化:

定义算法步骤骨架,并允许子类对一个或多个步骤进行实现

  • 定义模板流程
  • 定义抽象方法,子类强制实现
  • 如有必要定义钩子方法

模板模式很容易理解。

定义一个接口类

java 复制代码
public interface IApplyPayProcessor {

    void applyPay(ApplyPayRequest request);
}

定义抽象父类

java 复制代码
public abstract class AbstractApplyPayProcessor<T> implements IApplyPayProcessor{

    /**
     * 申请支付模板方法
     * @param request
     */
    @Override
    public void applyPay(ApplyPayRequest request) {
        //数据查询
        T t= query(request);
        //校验
        check(t);
        //业务逻辑处理
        process(t, request);
        //数据保存
        save(t);
        //通用业务逻辑处理
        sendMsg(t);
    }

    private void sendMsg(T t) {
        //发送业务逻辑完成通知 event、msg...
        ...实际代码省略
    }

    /**
     * 根据业务,子类实现
     * @param request
     * @return
     */
    public abstract T query(ApplyPayRequest request);

    /**
     * 根据业务,子类实现
     * @param t
     * @return
     */
    public abstract Boolean check(T t);

    /**
     * 根据业务,子类实现
     * @param t
     * @param request
     */
    public abstract void process(T t, ApplyPayRequest request);

    /**
     * 根据业务,子类实现
     * @param t
     */
    public abstract void save(T t);
}

子类实现

对于业务A,继承AbstractApplyPayProcessor,并根据自身业务的逻辑,实现相应的方法。

对于业务B、业务C也是类似的代码,只需要实现相应的方法,无需对业务流程进行重新的编排,毕竟父类已经定义了骨架方法。

java 复制代码
public class BizAApplyPayProcessor extends AbstractApplyPayProcessor<BizADO>{

    @Override
    public BizADO query(ApplyPayRequest request) {
        return new BizADO();
    }

    @Override
    public Boolean check(BizADO o) {
        //..子类业务逻辑省略
        return Boolean.TRUE;
    }

    @Override
    public void process(BizADO o, ApplyPayRequest request) {
        //..子类业务逻辑省略
    }

    @Override
    public void save(BizADO o) {
        //..子类业务逻辑省略
    }
}

工厂模式

经过第一步模板模式优化后,我们该怎么使用呢

通常我们会在service层这样使用:

java 复制代码
public void doApplyPay(ApplyPayRequest request) {
				....
				其他业务逻辑
				if (request.getType().equals(A)) {
            BizAApplyPayProcessor processor = new BizAApplyPayProcessor();
            processor.applyPay(request);
        } else if (request.getType().equals(B)) {
            BizBApplyPayProcessor processor = new BizBApplyPayProcessor();
            processor.applyPay(request);
        } else if (request.getType().equals(C)) {
            BizCApplyPayProcessor processor = new BizCApplyPayProcessor();
            processor.applyPay(request);
        }
				..... 其他业务逻辑
    }

我们很大概率会按照上述代码的编写方式,先通过if/else判断,然后使用new这个关键字,实例化对应的实体,或者Spring的方式注入对应的方式。

new代表着实例化,意味着我们的代码需要依赖具体的实现,而通常推荐的编程是面向接口而不是面向实现。

当有新的类型,比如业务D被添加进来时,这个service需要修改,if/else逻辑需要改变,不符合开闭原则,设计模式的核心的一点就是封装可变的部分。

可以使用工厂模式if/else封装在单独的类,伪代码如下:

java 复制代码
public class BizFactory {

    public static IApplyPayProcessor getApplyPayProcessor(String bizType) {
        IApplyPayProcessor processor;
        if (A) {
            processor = new BizAApplyPayProcessor();
            processor.applyPay(request);
        } else if (B) {
            processor = new BizBApplyPayProcessor();
            processor.applyPay(request);
        } else if (C) {
            processor = new BizCApplyPayProcessor();
            processor.applyPay(request);
        }
        return processor;
    }
}

//service层使用工厂类直接获取对应的实例
public class ClientService {

    public void doApplyPay(ApplyPayRequest request) {
        BizFactory.getApplyPayProcessor(request.getType());
    }
}

这样做起来看起来只是将代码从service层移动到了一个BizFactory中,问题依然是存在的,有什么好处呢?

  • service层不再随着实现的变化而变化,尽量往单一职责去靠拢,注意我这里说的是尽量,因为往往我们会做一个取舍。
  • BizFactory可以有很多业务工厂,其他业务service以后也不需要变化,只需要变化BizFactory

策略模式

我们可以看到对于工厂本身而言,虽然我们将对象的使用和创建 已经分离开来,使用工厂模式来专注于对象的创建,当新增业务类型时,比如业务D时,我们需要修改工厂模式,不符合开闭原则

如何进行进一步优化呢,让工厂模式不随着业务类型的增加而进行修改呢。

答案即是使用策略模式

定义一系列的算法或策略,并将每个算法封装在单独的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。

step1 先定一个枚举BizTypeEnum 用来标识业务类型,也就是策略的集合。

java 复制代码
@Getter
public enum BizTypeEnum {

    A(1, "业务A"),
    B(2, "业务B"),
    C(3, "业务C"),
    D(4, "业务D"),
    ;

    private int code;
    private String msg;

    BizTypeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    /**
     * 整形值转换为枚举类
     *
     * @param value 值
     * @return 枚举类
     */
    public static BizTypeEnum valueOf(int value) {
        for (BizTypeEnum anEnum : values()) {
            if (value == anEnum.getCode()) {
                return anEnum;
            }
        }
        return null;
    }
}

step2 定义一个注解,ApplyPay用来标记策略类

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplyPay {

    BizTypeEnum bizType() default BizTypeEnum.A;
}

step3 使用注解@ApplyPay,改造标记策略类,

java 复制代码
@ApplyPay(bizType = BizTypeEnum.A)
public class BizAApplyPayProcessor extends AbstractApplyPayProcessor<BizADO>{

    @Override
    public BizADO query(ApplyPayRequest request) {
        return new BizADO();
    }

    @Override
    public Boolean check(BizADO o) {
        //..子类业务逻辑省略
        return Boolean.TRUE;
    }

    @Override
    public void process(BizADO o, ApplyPayRequest request) {
        //..子类业务逻辑省略
    }

    @Override
    public void save(BizADO o) {
        //..子类业务逻辑省略
    }
}

@ApplyPay(bizType = BizTypeEnum.B)
public class BizBApplyPayProcessor extends AbstractApplyPayProcessor<BiZBDO>{

    @Override
    public BiZBDO query(ApplyPayRequest request) {
        return null;
    }

    @Override
    public Boolean check(BiZBDO biZBDO) {
        return null;
    }

    @Override
    public void process(BiZBDO biZBDO, ApplyPayRequest request) {

    }

    @Override
    public void save(BiZBDO biZBDO) {

    }
}

step4 识别加载封装策略类,改造工厂类

可以借助Spring的拓展能力,识别策略类的注解,并将策略实例化后,使用集合保存,当然如果没有使用Spring框架,也可以借助反射等来进行此动作。

java 复制代码
public class BizFactory implements ApplicationListener<ContextRefreshedEvent> {

    private static Map<BizTypeEnum, IApplyPayProcessor> APPLY_PAY_MAP = new ConcurrentHashMap<>(8);

    public static IApplyPayProcessor getApplyPayProcessor(BizTypeEnum bizType) {
        return APPLY_PAY_MAP.get(bizType);
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();

        //申请支付工厂初始化
        Map<String, Object> beansWithAnno = applicationContext.getBeansWithAnnotation(ApplyPay.class);
        if (beansWithAnno != null) {
            beansWithAnno.forEach((key, value) -> {
                BizTypeEnum bizType = value.getClass().getAnnotation(ApplyPay.class).bizType();
                APPLY_PAY_MAP.put(bizType, (IApplyPayProcessor) value);
            });
        }
    }
}

使用策略模式改造完之后,显而易见的改造后,不同的业务被封装在不同的类中,工厂模式也无需知晓每个类,只需要根据业务类型加载即可。

每次新增业务类型时,只需要新增策略类即可,无需对servicefactory进行修改。

相关推荐
计算机毕设指导69 分钟前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
Gu Gu Study11 分钟前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言
Chris _data14 分钟前
二叉树oj题解析
java·数据结构
牙牙70519 分钟前
Centos7安装Jenkins脚本一键部署
java·servlet·jenkins
paopaokaka_luck27 分钟前
[371]基于springboot的高校实习管理系统
java·spring boot·后端
以后不吃煲仔饭39 分钟前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师40 分钟前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
The_Ticker1 小时前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
小白不太白9501 小时前
设计模式之 观察者模式
观察者模式·设计模式
大数据编程之光1 小时前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink