Spring Boot项目实现策略模式、简单工厂与模板方法融合实战

后端 潘老师 17小时前 6 ℃ (0) 扫码查看

今天咱们讲讲在Spring Boot项目开发中,深入探讨策略模式、简单工厂模式和模板方法模式是如何巧妙融合,发挥强大作用的。本文将结合一个真实的“数据导出”业务需求,带大家一步步领略这几种设计模式协同工作的魅力。

一、需求背景

假设我们正在开发一个涉及多家银行数据导入导出的应用模块。在实际业务场景中,不同银行的数据处理逻辑往往大相径庭,而且随着业务的发展,新的银行可能会不断加入,原有的银行数据处理细节也可能发生变化。这就给我们的系统设计带来了不小的挑战:

  • 要能根据不同的银行编码,准确找到对应的处理策略,实现定制化的数据处理。
  • 当有新银行加入时,尽量避免大规模修改已有的代码,遵循开闭原则,保证系统的扩展性。
  • 还得提供一套基础的默认逻辑,以防某个银行的处理器未实现相关功能时,程序不会直接崩溃。

二、核心设计思路

为了应对这些挑战,我们采用了策略模式、简单工厂模式和模板方法模式相结合的设计方案。

  • 策略模式:简单来说,就是定义一个统一的抽象策略接口(或者抽象类)。把每个银行的数据处理逻辑都当作一个具体的策略来实现,这样就能在运行时根据实际情况灵活切换不同的策略,实现不同银行数据处理逻辑的动态替换。
  • 简单工厂模式:创建一个叫BankStrategyHolder的策略持有者。它就像一个“策略工厂”,能根据银行编码,从众多策略中动态挑选出对应的策略实例。借助Spring的注解读取元数据功能,它还能自动完成策略的映射,大大简化了策略对象的创建过程。
  • 模板方法模式:通过抽象类BankStrategy来发挥作用。这个抽象类定义了数据导入导出的基础方法签名,并且提供了默认实现。不过默认实现只是抛出一个不支持的异常,提醒子类去实现具体的逻辑。这样一来,既保证了整体算法框架的统一,又让子类能根据自身需求覆盖特定的方法,实现定制化的处理。

这种设计方式不仅能满足业务灵活扩展的需求,还能让代码结构更加清晰,后期维护起来也更加轻松。

三、关键代码

(一)常量定义

先来看常量定义部分,这部分代码定义了银行编码和默认标识常量,方便在整个项目中统一调用和管理。

public class Constants {
    /** 默认策略标识 */
    public static final String DEFAULT_STRATEGY_NAME = "default";
}

public class BankConstants {
    public static final String FIRST_BANK = "0001";
    public static final String SECOND_BANK = "0002";
    public static final String THIRD_BANK = "0003";
    public static final String FOURTH_BANK = "0004";
}

这里的Constants类定义了默认策略标识,而BankConstants类则定义了各个银行对应的编码。在后续的代码中,我们会频繁使用这些常量来区分不同的银行和策略。

(二)自定义注解

为了让工厂能自动扫描和映射不同银行的策略实现,我们使用了自定义注解@StrategyIdentifier

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface StrategyIdentifier {
    String[] value();
}

这个注解被标记在具体的策略实现类上,value()方法用来指定该策略所对应的银行编码。通过这种方式,工厂就能轻松识别每个策略实现类对应的银行,实现自动映射。

(三)抽象策略模板

BankStrategy抽象类在整个设计中扮演着模板方法模式的关键角色。

public abstract class BankStrategy {

    /**
     * 导入数据,默认不支持。
     */
    public void importData() {
        throw new UnsupportedOperationException("此银行不支持导入数据功能!");
    }

    /**
     * 导出数据,默认不支持。
     */
    public void exportData() {
        throw new UnsupportedOperationException("此银行不支持导出数据功能!");
    }
}

它定义了数据导入和导出的方法,但默认实现都是抛出不支持的异常。这是因为不同银行的具体导入导出逻辑是不同的,需要子类去覆盖这些方法来实现各自的业务逻辑。

(四)具体策略实现

下面是各个银行具体的策略实现类,它们都继承自BankStrategy,并结合@StrategyIdentifier注解来完成不同银行的导入导出逻辑。

@Slf4j
@Component
@StrategyIdentifier({Constants.DEFAULT_STRATEGY_NAME})
public class DefaultBankStrategy extends BankStrategy {
    @Override
    public void importData() {
        log.info("默认银行数据导入逻辑");
    }

    @Override
    public void exportData() {
        log.info("默认银行数据导出逻辑");
    }
}

@Slf4j
@Component
@StrategyIdentifier({BankConstants.FIRST_BANK})
public class FirstBankStrategy extends BankStrategy{
    @Override
    public void importData() {
        log.info("第一银行数据导入逻辑");
    }

    @Override
    public void exportData() {
        log.info("第一银行数据导出逻辑");
    }
}

@Slf4j
@Component
@StrategyIdentifier({BankConstants.SECOND_BANK})
public class SecondBankStrategy extends BankStrategy{
    @Override
    public void importData() {
        log.info("第二银行数据导入逻辑");
    }

    @Override
    public void exportData() {
        log.info("第二银行数据导出逻辑");
    }
}

@Slf4j
@Component
@StrategyIdentifier({BankConstants.THIRD_BANK, BankConstants.FOURTH_BANK})
public class ThirdBankStrategy extends BankStrategy{
    // 不重写 importData,默认不支持
    @Override
    public void exportData() {
        log.info("第三、四银行数据导出逻辑");
    }
}

每个具体策略实现类都根据自身银行的业务需求,重写了importDataexportData方法。@StrategyIdentifier注解则明确指定了该策略对应的银行编码,方便工厂进行管理。

(五)策略持有者(简单工厂)

BankStrategyHolder类就像是一个“策略工厂”,负责管理所有的策略实例。

@Component
public class BankStrategyHolder {

    private final Map<String, BankStrategy> strategyMap = new HashMap<>();

    @Autowired
    public BankStrategyHolder(List<BankStrategy> bankStrategyList) {
        bankStrategyList.forEach(strategy -> {
            StrategyIdentifier annotation = strategy.getClass().getAnnotation(StrategyIdentifier.class);
            if (annotation != null) {
                for (String bankCode : annotation.value()) {
                    strategyMap.put(bankCode, strategy);
                }
            }
        });
    }

    /**
     * 根据银行编码获取对应策略,若不存在则返回默认策略。
     */
    public BankStrategy getByBankCode(String bankCode) {
        return strategyMap.getOrDefault(bankCode, strategyMap.get(Constants.DEFAULT_STRATEGY_NAME));
    }
}

在这个类中,通过@Autowired注解,Spring会自动注入所有的BankStrategy实现类。然后,它遍历这些策略实例,通过注解读取每个策略对应的银行编码,并将其存储在strategyMap中。getByBankCode方法则根据传入的银行编码,从strategyMap中获取对应的策略实例,如果找不到,则返回默认策略。

(六)外部调用控制器

StrategyController负责处理外部的请求,通过银行编码获取相应的策略,并调用数据导入导出方法。

@Slf4j
@RestController
@Tag(name = "策略模式示例")
public class StrategyController {

    @Resource
    private BankStrategyHolder bankStrategyHolder;

    @GetMapping("/importData")
    @Operation(summary = "导入数据")
    public void importData(@RequestParam String bankCode) {
        log.info("调用 [导入数据] - 银行编码: {}", bankCode);
        bankStrategyHolder.getByBankCode(bankCode).importData();
    }

    @GetMapping("/exportData")
    @Operation(summary = "导出数据")
    public void exportData(@RequestParam String bankCode) {
        log.info("调用 [导出数据] - 银行编码: {}", bankCode);
        bankStrategyHolder.getByBankCode(bankCode).exportData();
    }
}

在这个控制器中,通过注入BankStrategyHolder,获取对应的策略实例,然后根据请求的银行编码调用相应的导入或导出方法。这样,外部请求就能顺利地通过控制器,找到对应的策略并执行相应的业务逻辑。

四、效果验证

我们可以通过向接口/importData/exportData传递不同的银行代码,来验证系统的功能。

  • 当向/importData接口传入0001时,会执行第一银行的导入逻辑;传入0002时,执行第二银行的导入逻辑。
  • 传入00030004时,由于这两个银行在导入功能上没有具体实现,会抛出异常,提示导入功能不支持。
  • 传入其他未定义的银行代码(如0005)时,会执行默认的导入逻辑。

同样,在访问/exportData接口时:

  • 传入00010002会分别输出各自银行的导出逻辑。
  • 传入00030004会共用第三银行策略的导出逻辑。
  • 未匹配到的银行代码则执行默认导出逻辑。

通过这样的设计,当需要新增银行时,我们只需要新增对应的策略实现类,并添加相应的注解,而无需修改已有的核心代码,充分体现了系统的可扩展性。

五、思考与总结

通过这个实际案例,我们深刻体会到了这几种设计模式协同工作带来的强大优势:

  • 策略模式让不同银行的数据导入导出逻辑相互独立,隔离了业务差异,使得替换和扩展算法变得轻而易举。
  • 简单工厂模式自动管理策略类的创建和注入过程,让客户端代码变得简洁,降低了代码之间的耦合度。
  • 模板方法模式保证了公共方法逻辑的一致性,提供了默认行为,增强了系统的可靠性。

这种设计方式完全符合面向对象的设计原则,极大地提升了代码的可维护性和拓展性。随着业务的不断发展,新增银行业务逻辑时,我们只需要编写新的策略实现类,就能轻松融入现有系统,让系统始终保持简洁高效。

最后,建议大家在学习和实践过程中,充分利用Spring的依赖注入和自定义注解功能,进一步探索和实现自动扫描和策略注册机制,打造出更适合自己项目的灵活策略框架。

如果大家对文中的内容有任何疑问或者想法,欢迎在评论区留言讨论。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/17971.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】