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

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

现状

复制代码
  系统目前支持三种业务流程,业务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);
            });
        }
    }
}

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

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

相关推荐
金銀銅鐵3 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
福建佰胜张工4 小时前
SMC 薄型气爪 MHF2-16D1 深度解析:平行开闭、低剖面结构在自动化装配中的选型与落地
java·服务器·安全·自动化
catoop4 小时前
API Key 设计与安全生成方案:结构拆解、数据库设计与 Java 实现
java·数据库·安全
system_error_x4 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
驭渊的小故事5 小时前
Spring AI Alibaba 实战:从maven依赖治理到声明式工具调用
java·人工智能·spring
Elastic 中国社区官方博客5 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1885 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn6 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su6 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
我命由我123457 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法