策略模式使用

本文介绍了如何利用策略模式来设计公交计费系统,避免使用ifelse语句,提高代码可扩展性。通过定义算法接口和实现不同折扣策略的类,如现金、公交卡、电子公交卡和NFC,将这些策略注入到上下文管理中,方便根据业务类型动态获取和执行计费算法。
摘要由CSDN通过智能技术生成

策略模式

策略模式就是定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换;它可以替换继承关系,避免使用多重条件转移语句

典型应用场景

  • 根据不同的类型选择不同的计费策略、处理机制

参考:https://blog.csdn.net/u010247622/article/details/106220318/

  • 容错恢复机制,当程序出现错误时,使用备选方案

参考:https://www.cnblogs.com/LoveShare/p/10953940.html

算法结构

在这里插入图片描述

如图,该算法,一般先定义算法接口,然后根据不同的算法策略实现具体的算法;然后将算法注入到StrategyContext中,之后Client根据不同的类型获取对应的算法,执行之。

使用举例

公交计费时可以使用多种方式,常见的有现金、公交卡、电子公交卡、NFC等,每种方式计价的方式不同,比如:

  • 现金不打折
  • 公交卡打85折
  • 电子公交卡打9折
  • NFC打8折

现在需要实现公交计费。

典型的方式是使用if else判断不同场景进行计算,但是扩展性差,代码也不美观。

现在采用自定义注解结合策略模式实现计费,代码接口如下:

在这里插入图片描述

首先定义个枚举类,列举主要的业务类型

public enum CalculateTypeEnum {
    NFC("nfc"),
    CARD("card"),
    E_CARD("eCard"),
    CASH("cash");
    private String type;
    CalculateTypeEnum(String type){
        this.type=type;
    }

    public String getType(){
        return type;
    }
}

然后自定义一个注解@CalculateStrategy,该注解主要用于后面的具体算法上,作为标识使用

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CalculateStrategy {
    CalculateTypeEnum value() default CalculateTypeEnum.CASH;
}

实现策略模式最主要的是算法之间具有可替换性,这样的话需要定义算法接口,具体的算法实现了相同的接口,就具备了可替换性,接口定义如下:

public interface ICalculateStrategy {
    Double calculateAmount(Double oriAmount);
}

分别先现金、实体公交卡、电子公交卡、NFC的计算策略

@Service
@Slf4j
@CalculateStrategy(CalculateTypeEnum.CASH)
public class CashStrategy implements ICalculateStrategy{
    @Override
    public Double calculateAmount(Double oriAmount) {
        log.info("Using CashStrategy");
        return oriAmount;
    }
}
@Service
@Slf4j
@CalculateStrategy(CalculateTypeEnum.CARD)
public class CardStrategy implements ICalculateStrategy{
    @Override
    public Double calculateAmount(Double oriAmount) {
        log.info("Using CardStrategy");
        return oriAmount*0.9;
    }
}
@Service
@Slf4j
@CalculateStrategy(CalculateTypeEnum.E_CARD)
public class ECardStrategy implements ICalculateStrategy {
    @Override
    public Double calculateAmount(Double oriAmount) {
        log.info("Using ECardStrategy");
        return oriAmount * 0.85;
    }
}
@Service
@Slf4j
@CalculateStrategy(CalculateTypeEnum.NFC)
public class NFCStrategy implements ICalculateStrategy{
    @Override
    public Double calculateAmount(Double oriAmount) {
        log.info("Using NFCStrategy");
        return oriAmount*0.8;
    }
}

可以看到上面通过@Service注解将算法注入到IOC容器,使用@CalculateStrategy注解标识了不同类型的计费算法策略,现在需要将策略统一放在一个上下文中进行管理

@Component
public class CalculateStrategyContext implements InitializingBean {
    /**
     * 算法策略map
     */
    private static Map<String, Class<ICalculateStrategy>> strategyMap;

    /**
     * 算法策略注入map
     *
     * @throws Exception 异常
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        //从IOC中获取所有加了@CalculateStrategy注解的类
        Map<String, Object> beansMap = AppContextUtil.appContext.getBeansWithAnnotation(CalculateStrategy.class);
        // 将其按照注解上的type放入map中
        beansMap.forEach((k, v) -> {
            Class<ICalculateStrategy> strategyClass = (Class<ICalculateStrategy>) v.getClass();
            String type = strategyClass.getAnnotation(CalculateStrategy.class).value().getType();
            if (Objects.isNull(CalculateStrategyContext.strategyMap)) {
                CalculateStrategyContext.strategyMap = new HashMap<>();
            }
            CalculateStrategyContext.strategyMap.put(type, strategyClass);
        });
    }

    /**
     * 根据类型获取策略
     *
     * @param type 类型
     * @return 策略
     */
    public static ICalculateStrategy getStrategyByType(String type) {
        Class<ICalculateStrategy> strategyClass = strategyMap.get(type);
        if (Objects.isNull(strategyClass)) {
            throw new RuntimeException("No strategy exists for " + type);
        }
        return (ICalculateStrategy) AppContextUtil.getBean(strategyClass);
    }
}

工具类AppContextUtil

@Component
public class AppContextUtil implements ApplicationContextAware {

    /**
     * 应用上下文
     */
    public static ApplicationContext appContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        AppContextUtil.appContext = applicationContext;
    }
    
    public static Object getBean(Class<?> clz) {
        return appContext.getBean(clz);
    }
}

最后编写测试用例测试

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyBatisApplication.class)
public class CalculateStrategyContextTest {

    @Before
    public void before() throws Exception {
    }

    @After
    public void after() throws Exception {
    }

    /**
     * Method: getStrategyByType(String type)
     */
    @Test
    public void testGetStrategyByType() throws Exception {
        Double amount = 1000d;
        ICalculateStrategy cashStrategy = CalculateStrategyContext.getStrategyByType(CalculateTypeEnum.CASH.getType());
        System.out.println("cashStrategy:" + cashStrategy.calculateAmount(amount));
        ICalculateStrategy cardStrategy = CalculateStrategyContext.getStrategyByType(CalculateTypeEnum.CARD.getType());
        System.out.println("cardStrategy:" + cardStrategy.calculateAmount(amount));
        ICalculateStrategy eCardStrategy = CalculateStrategyContext.getStrategyByType(CalculateTypeEnum.E_CARD.getType());
        System.out.println("eCardStrategy:" + eCardStrategy.calculateAmount(amount));
        ICalculateStrategy nfcStrategy = CalculateStrategyContext.getStrategyByType(CalculateTypeEnum.NFC.getType());
        System.out.println("nfcStrategy:" + nfcStrategy.calculateAmount(amount));
    }


} 

运行结果

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值