【日常业务开发】策略+工厂模式优化 if...else判断逻辑

策略模式

策略模式是一种行为设计模式,它定义了一系列算法,并将每个算法封装成独立的类,使得它们可以互相替换。策略模式使得算法的变化独立于使用算法的客户端。

策略模式由三个部分组成:策略接口、具体策略类和环境类。策略接口定义了所有具体策略类都需要实现的方法;具体策略类实现了策略接口,并提供不同的算法实现;环境类持有一个策略对象,并利用策略对象执行具体的算法。

策略模式在以下情况下特别适用:

  • 需要根据不同的条件或参数选择不同的算法或策略。例如,在一个电商网站中,根据用户的购买历史、地理位置等信息,选择不同的优惠策略或物流配送策略。

  • 需要封装一组相似的算法,以便在不同的情况下使用不同的算法。例如,在一个游戏中,根据不同的敌人类型和玩家状态选择不同的战斗策略。

  • 有一组类似的行为,但是可能会发生变化。通过将每个行为封装为一个具体策略类,可以更灵活地添加、删除或修改行为。例如,在一个日程管理应用中,可以根据用户的不同需求设置不同的提醒策略。

  • 不希望暴露复杂的条件语句或逻辑判断代码。使用策略模式可以将条件语句封装在具体策略类中,使得代码更加清晰、易于理解和维护。

总的来说,策略模式适用于需要根据不同的条件选择不同算法或策略,并且希望将算法或策略的实现与使用代码解耦的情况。它能够提供灵活性、扩展性和可维护性,使得代码更具可读性和可重用性。

工厂模式

工厂模式是一种创建型设计模式,它提供了一种创建对象的接口,但具体的对象创建过程由子类或子工厂类来决定。通过使用工厂模式,可以将对象的创建和使用解耦,使得代码更加灵活、可扩展和易于维护。

工厂模式在以下情况下特别适用:

  • 当需要创建复杂对象或对象之间存在复杂的依赖关系时,可以使用工厂模式来封装对象的创建过程,并隐藏创建细节。

  • 当需要创建的对象可能会随着程序的运行时条件变化而变化时,可以使用工厂模式来动态地选择合适的对象创建方式。

  • 当希望将对象的创建逻辑集中在一处,以便于维护和管理时,可以使用工厂模式来统一对象的创建过程。

工厂模式有多种不同的变体,包括简单工厂模式、工厂方法模式和抽象工厂模式。简单工厂模式通过一个工厂类来创建对象,工厂方法模式通过子类来创建对象,抽象工厂模式通过一组相关的工厂类来创建对象。

总的来说,工厂模式适用于创建复杂对象、解耦对象的创建和使用、动态选择对象创建方式以及集中管理对象的创建过程等情况。它提供了一种灵活、可扩展和可维护的方式来创建对象。

场景

业务中经常有支付业务,项目刚开始所有业务使用支付宝支付,后面湖南地区需要使用微信支付,然后湖北又需要使用易宝支付,后期可能还有扩展。如果使用if…else逻辑,业务中经常要写的代码:

public static void pay() {
    // String fruit = "aliPay";
    // String fruit = "yiBaoPay";
    String payType = "weiXinPay";
    if ("aliPay".equals(payType)){
        log.info("===调用支付宝接口发起支付业务===");
    } else if ("yiBaoPay".equals(payType)){
        log.info("===调用易宝接口发起支付业务===");
    } else {
        log.info("===调用微信接口发起支付业务===");
    }
}

如何优雅的处理消除if…else逻辑,方面后期扩展呢?策略+工厂模式优化

策略+工厂模式优化

在这里插入图片描述

利用Spring自动注入的特点处理

支付接口IPay和具体实现类AliPay、WeiXinPay、YiBaoPay

public interface IPay {
    String pay();
}

@Service
@Slf4j
public class AliPay implements IPay {

    @Override
    public String pay() {
        log.info("===调用支付宝接口发起支付业务===");
        return "调用支付宝接口发起支付业务";
    }
}

@Service
@Slf4j
public class WeiXinPay implements IPay {
    @Override
    public String pay() {
        log.info("===调用微信接口发起支付业务===");
        return "调用微信接口发起支付业务";
    }
}

@Service
@Slf4j
public class YiBaoPay implements IPay {
    @Override
    public String pay() {
        log.info("===调用易宝接口发起支付业务===");
        return "调用易宝接口发起支付业务";
    }
}

工厂类PayFactory

@Component
public class PayFactory {
    /**
     * 利用Spring自动注入的特点。Key为bean的名称、value为IPay接口对应的bean实现类
     */
    @Autowired
    Map<String, IPay> map;

    public  IPay getInstance(String type) {
        return map.get(type);
    }
}
@RestController
public class PayController {

    @Autowired
    private PayFactory payFactory;

    @GetMapping("/alipay")
    public String aliPay(String type) {
        IPay pay = payFactory.getInstance(type);
        return pay.pay();
    }
}

Key为bean的名称、value为IPay接口对应的bean实现类。对应的业务类型不好扩展

继承InitializingBean+静态工厂方法调用处理

public interface IPay extends InitializingBean {
    String pay();
}


@Service
@Slf4j
public class AliPay implements IPay {

    @Override
    public String pay() {
        log.info("===调用支付宝接口发起支付业务===");
        return "调用支付宝接口发起支付业务";
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        PayFactory.registerHandler("ali", this);
    }
}

@Service
@Slf4j
public class WeiXinPay implements IPay {
    @Override
    public String pay() {
        log.info("===调用微信接口发起支付业务===");
        return "调用微信接口发起支付业务";
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        PayFactory.registerHandler("weixin", this);
    }
}

@Service
@Slf4j
public class YiBaoPay implements IPay {
    @Override
    public String pay() {
        log.info("===调用易宝接口发起支付业务===");
        return "调用易宝接口发起支付业务";
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        PayFactory.registerHandler("yibao", this);
    }
}

工厂类PayFactory,暴露注册bean到工厂容器Map对象方法,提供根据type类型查询bean的静态方法

public class PayFactory {
    private static final Map<String, IPay> PAY_MAP = new HashMap<>();
    /**
     * 获取对应实例Bean
     * @param type
     * @return
     */
    public static IPay getInstance(String type) {
        return PAY_MAP.get(type);
    }

    /**
     * 注册bean到工厂容器对象
     * @param handlerName
     * @param pay
     */
    public static void registerHandler(String handlerName, IPay pay) {
        PAY_MAP.put(handlerName, pay);
    }
}

注解+CommandLineRunner+ApplicationContextAware处理/ApplicationListener<ContextRefreshedEvent>

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PayCode {
    String value();
    String name();
}

public interface IPay {
    String pay();
}

@PayCode(value = "alia", name = "支付宝支付")
@Service
@Slf4j
public class AliPay implements IPay {

    @Override
    public String pay() {
        log.info("===调用支付宝接口发起支付业务===");
        return "调用支付宝接口发起支付业务";
    }
}

@PayCode(value = "weixin", name = "微信支付")
@Service
@Slf4j
public class WeiXinPay implements IPay {
    @Override
    public String pay() {
        log.info("===调用微信接口发起支付业务===");
        return "调用微信接口发起支付业务";
    }
}

@PayCode(value = "yibao", name = "易宝支付")
@Service
@Slf4j
public class YiBaoPay implements IPay {
    @Override
    public String pay() {
        log.info("===调用易宝接口发起支付业务===");
        return "调用易宝接口发起支付业务";
    }
}

第一种方式: 工厂类PayFactory实现Spring生命周期函数在run方法中处理被标记的@PayCode的bean对象加入到静态容器Map中:

  • CommandLineRunner:当前Bean被IOC容器装配完成调用run()方法

  • ApplicationContextAware: 获取容器对象ApplicationContext

@Component
public class PayFactory implements CommandLineRunner, ApplicationContextAware {

    private ApplicationContext applicationContext;

    private static final Map<String, IPay> PAY_MAP = new HashMap<>();

    /**
     * 获取对应实例Bean
     * @param type
     * @return
     */
    public static IPay getInstance(String type) {
        return PAY_MAP.get(type);
    }


    @Override
    public void run(String... args) throws Exception {
        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(PayCode.class);
        if (beansWithAnnotation !=null ) {
            beansWithAnnotation.forEach((k,v)->{
                PayCode payCodeAnn = v.getClass().getAnnotation(PayCode.class);
                PAY_MAP.put(payCodeAnn.value(), (IPay)v);
            });
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

第二种方式: 实现ApplicationListener<ContextRefreshedEvent>监听事件接口代替CommandLineRunner, ApplicationContextAware

ContextRefreshedEvent事件: ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在ConfigurableApplicationContext接口中使用 refresh()方法来发生。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用。

@Component
public class PayFactory implements ApplicationListener<ContextRefreshedEvent> {

    private static final Map<String, IPay> PAY_MAP = new HashMap<>();

    /**
     * 获取对应实例Bean
     * @param type
     * @return
     */
    public static IPay getInstance(String type) {
        return PAY_MAP.get(type);
    }
    
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(PayCode.class);
        if (beansWithAnnotation !=null ) {
            beansWithAnnotation.forEach((k,v)->{
                PayCode payCodeAnn = v.getClass().getAnnotation(PayCode.class);
                PAY_MAP.put(payCodeAnn.value(), (IPay)v);
            });
        }
    }
}
@RestController
public class PayController {
    
    @GetMapping("/alipay")
    public String aliPay(String type) {
        IPay pay = PayFactory.getInstance(type);
        return pay.pay();
    }
}

Map+函数式接口代替策略模式

用上了Java8的新特性lambda表达式

  • 判断条件放在key中
  • 对应的业务逻辑放在value中

这样子写的好处是非常直观,能直接看到判断条件对应的业务逻辑

需求:根据优惠券(资源)类型resourceType和编码resourceId查询派发方式grantType

在这里插入图片描述

上代码:

@Service
public class QueryGrantTypeService {

    @Autowired
    private GrantTypeService grantTypeService;

    private Map<String, Function<String, String>> grantTypeMap = new HashMap();

    /**
     * 初始化业务分派逻辑,代替了if-else部分
     * key: 优惠券类型
     * value: lambda表达式,最终会获得该优惠券的发放方式
     */
    @PostConstruct
    public void dispatcherInit() {
        grantTypeMap.put("红包", resourceId -> grantTypeService.redPaper(resourceId));
        grantTypeMap.put("购物券", resourceId -> grantTypeService.shopping(resourceId));
        grantTypeMap.put("qq会员", resourceId -> grantTypeService.QQVip(resourceId));
    }


    public String getResult(String resourceType, String resourceId) {
        // 根据 优惠券类型resourceType、编码resourceId 去查询 发放方式grantType
        Function<String, String> result = grantTypeMap.get(resourceType);
        if (Objects.nonNull(result)) {
            //传入resourceId 执行这段表达式获得String型的grantType
            return result.apply(resourceId);
        }
        return "查询不到该优惠券的发放方式";
    }
}

如果单个 if 语句块的业务逻辑有很多行的话,我们可以把这些 业务操作抽出来,写成一个单独的Service,即:

@Service
public class GrantTypeService {

    public String redPaper(String resourceId){
        //红包的发放方式
        return "每周末9点发放";
    }
    public String shopping(String resourceId){
        //购物券的发放方式
        return "每周三9点发放";
    }
    public String QQVip(String resourceId){
        //qq会员的发放方式
        return "每周一0点开始秒杀";
    }
}

入参String resourceId是用来查数据库的,这里简化了,传参之后不做处理

@RestController
public class GrantTypeController {

    @Autowired
    private QueryGrantTypeService queryGrantTypeService;

    @GetMapping("/grantType/{resourceType}/{resourceId}")
    public String test(@PathVariable String resourceType, @PathVariable String resourceId){
        return queryGrantTypeService.getResult(resourceType, resourceId);
    }
}

Map+函数式接口通过Map.get(key)来代替 if-else的业务分派,能够避免策略模式带来的类增多、难以俯视整个业务逻辑的问题。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李熠漾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值