(优化if-else)工厂+ 策略设计模式在 Springboot 项目中使用案例

工厂模式和策略模式:

在Java的设计模式中,工厂模式和策略模式都是很常见且实用的模式。它们各自解决了不同的问题,在复杂的系统设计中,这两种模式往往会结合使用,以提升代码的灵活性、可维护性和扩展性。
1、工厂模式(Factory Pattern)是一种创建型设计模式,旨在定义一个接口来创建对象,但让子类决定实例化哪一个类。工厂模式让一个类的实例化延迟到其子类。
2、策略模式(Strategy Pattern)是一种行为型设计模式,定义了一系列算法,并将每个算法封装起来,使它们可以互换使用。策略模式使得算法可以在不影响客户端的情况下发生变化。
1. 需求背景
假设我们需要开发一个订单处理系统,系统需要根据不同的支付方式(如信用卡、PayPal、比特币等)进行不同的处理。不同的支付方式有不同的处理策略,并且系统需要支持将来扩展新的支付方式。
2. 设计思路
使用策略模式:将不同的支付方式封装为不同的策略,实现支付的多样化处理。
使用工厂模式:工厂模式负责根据用户选择的支付方式创建和选择相应的支付策略对象。执行业务逻辑。

业务场景:

1、营销系统,根据不同业务渠道推送消息,比如:微信消息推送、钉钉工作通知、短信、邮件通知
2、系统支付,业务选择不同支付方式,执行不同的业务逻辑和处理措施。比如:微信、支付宝、银联等
3、系统登录,根据需求选择不同登录方式,触发后台不同的业务执行逻辑。比如:微信、钉钉、短信、支付宝等不同的登录渠道进行授权,完成业务系统登录

解决方案(如下图):

1、在业务层使用 if-else 判断类型,去执行不同的业务逻辑。
2、使用工厂模式+策略设计模式,同时结合SpringBoot注解选择不同的策略,实现不同的业务逻辑。

代码示例

定义枚举

@AllArgsConstructor
@Getter
public enum SaleOrderEnum {

    SHOP_SALE_ORDER("10001", "店铺业务零售单"),
    SUPPLY_SALE_ORDER("10002","供应链直销单"),
    ;

    private static final Map<Long, SaleOrderEnum > ORDER_MAP = new HashMap<>(32);

    static {
        for (SaleOrderEnum orderStatusEnum : SaleOrderEnum .values()) {
            ORDER_STATUS_MAP.put(orderStatusEnum.getCode(), orderStatusEnum);
        }
    }

    public static SaleOrderEnum findByCode(String code) {
        // 这里可以直接调用getSaleOrdeEnum()方法也可以获取。
        for (SaleOrderEnum orderEnum : SaleOrderEnum.values()) {
            if (orderEnum.getCode().equals(code)) {
                return orderEnum;
            }
        }
        throw new IllegalArgumentException("Please check the passed parameters");
    }

    private String code;
    private String name;

   public static SaleOrderEnum getSaleOrdeEnum(Long code) {
        return ORDER_STATUS_MAP.get(code);
    }
}

定义注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface OrderStrategyType {
    SaleOrderEnum orderType();
}

描述:定义这个注解的目的,是在不同的策略上使用的,在工厂中就可以根据注解查找到所有的策略,并保存进 map 中映射起来,一旦有参数进来,就去 map 中去查找对应的策略,就可以执行对应的方法了,减少if-else代码块优化代码,同时可以保证代码的扩展性。

定义策略类的接口

首先定义一个策略接口,然后编写业务类去实现

public interface OrderStrategy {
    /**
     * 查询数据方法
     *
     * @return 返回结果
     */
    List<Object> executeOrderBiz();
}
1、门店业务实现类、实现工单策略处理接口,编写业务执行逻辑
@Slf4j
@Component
@RequiredArgsConstructor
@OrderStrategyType(orderType = SaleOrderEnum.SHOP_SALE_ORDER)
public class ShopSaleOrderStrategy implements OrderStrategy {
    @Override
    public List<Object> executeOrderBiz() {
        log.info("执行门店销售单业务功能......");
        return Arrays.asList("业务工单1", "业务工单2", "业务工单3");
    }
}
2、供应链业务实现类、实现工单策略处理接口,编写业务执行逻辑
@Slf4j
@Component
@RequiredArgsConstructor
@OrderStrategyType(orderType = SaleOrderEnum.SUPPLY_SALE_ORDER)
public class SupplySaleOrderStrategy implements OrderStrategy {
    @Override
    public List<Object> executeOrderBiz() {
        log.info("执行供应链销售单业务功能......");
        return Arrays.asList("供应链工单1","供应链工单2");
    }
}
注意:我们要把具体的业务实现类策略交给 Spring 管理,所以加上了 @Component 注解。

定义工厂类(业务调用通用入口)

@Component
@RequiredArgsConstructor
public class OrderStrategyFactory {
    /**
     * Spring会注入所有OrderStrategy的业务实现类
     */
    private final List<OrderStrategy> strategyList;

    private static Map<SaleOrderEnum, OrderStrategy> map = new ConcurrentHashMap<>();

    public static OrderStrategy getStrategy(String code){
        return map.get(SaleOrderEnum.findByCode(code));
    }

    public OrderStrategyFactory(List<OrderStrategy> strategyList) {
        this.strategyList = strategyList;
    }

    @PostConstruct
    public void init(){
        strategyList.forEach(i -> {
            OrderStrategyType annotation = AnnotationUtils.findAnnotation(i.getClass(), OrderStrategyType.class);
            map.put(annotation.orderType(),i);
        });
    }
}
上面案例代码中我们已经把具体的策略,像 SupplySaleOrderStrategy、ShopSaleOrderStrategy 这些策略的实现,交给 spring 管理了,也就是说我们可以从 spring 的容器中获取到对应的 bean,当然也可以进行依赖注入,这就是Spring中IOC容器特性。
所以在工厂类中,我们定义了一个 strategyList 属性,并且使用 @RequiredArgsConstructor 注解,就是使用构造器的方式将属性注入。所以当 spring 容器启动的时候,就会查找所有策略,并注入到工厂类的属性当中。这是 spring 的依赖注入的原理。
我们使用 @PostContruct 注解,当工厂类加载完毕之后,就会执行 init()方法。这时候,因为 orderStrategyFactory 这个 bean 已经在 spring 容器中加载完毕了,并且也注入了属性,所以这个 strategyList 就有值了,我们可以遍历这个列表,获取到对应 OrderStrategyType 这个注解的信息,然后放入到 map 当中,后面我们就可以根据枚举类型将不同的策略对应起来了。

使用层面:业务调用示例(使用案例)

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping(("/getList")
    public List<Object> getList(@RequestParam String type){
        OrderStrategy strategy = OrderStrategyFactory.getStrategy(type);
        return strategy.executeOrderBiz();
    }
}

总结:

这样就可以避免代码中的if-else,同时可以动态增加业务实现类。
比如增加其他业务实现类,直接实现OrderStrategy 接口即可,同时修改每个执行业务的逻辑,不会相互影响。使用时,我们就可以在枚举类中加入对应的类型,在具体的策略实现类中加入注解,并指定对应的枚举类型,这样我们就可以完成我们的业务需求了。

补充描述:上面实现接口、也可以继承抽象类

定义业务抽象类

public abstract class OrderStrategy {
    public abstract List<Object> executeOrderBiz();
}

使用案例:将实现类中原来的implements 变成extends 其他代码完全一致

@Slf4j
@Component
@RequiredArgsConstructor
@OrderStrategyType(orderType = SaleOrderEnum.SHOP_SALE_ORDER)
public class ShopSaleOrderStrategy extends OrderStrategy {
    @Override
    public List<Object> executeOrderBiz() {
        log.info("执行门店销售单业务功能......");
        return Arrays.asList("业务工单1", "业务工单2", "业务工单3");
    }
}
【精彩语录】:我知道你是普通人,我们大家都是普通人。但是你知道吗?这个世界上大部分的奇迹,只不过是普普通通的人们,将心意化作了行动而已。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350