策略模式学习笔记

1.概念

参考《JAVA与模式》的描述:

策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。

策略模式使得算法可以在不影响到客户端的情况下发生变化。

2.层次结构

  • 目的:策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。

  • 实现:策略模式通常把一个系列的算法包装到一系列的策略类里面,通常是作为一个接口的子类。即:“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。

  • UML图如下:

    image.png

     这个模式涉及到三个角色:
     (1)环境(Context)角色:持有一个Strategy的引用。
     (2)抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实
    现。此角色给出所有的具体策略类所需的接口。
    (3)具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

3.实际案例

笔者在公司重构金额计算模块时,需要根据产品类型 + 收费代码,计算应收金额

业务逻辑如下:

  • 根据产品类型确定:费率rate + 收费规则rule
  • 每个产品有001和002两种收费项。根据产品费率rate,去计算这两种收费项的应收金额

原始代码是这样:

    // 产品类型
    int type = 1;
    // 往返都可以直达
    if (type == 1) {
        // 根据费率rate1 + 收费规则rule1, 计算收费代码为:001,002的款项应收金额
        return;
    }

    // 产品类型为2
    if (type == 2) {
        // 根据费率rate2 + 收费规则rule2, 计算收费代码为:001,002的款项应收金额
        return;
    }
    // 产品类型为3
    if (type == 3) {
        // 根据费率rate3 + 收费规则rule3, 计算收费代码为:001,002的款项应收金额
        return;
    }
    // 产品类型为4
    else{
        // 根据费率rate3 + 收费规则rule3, 计算收费代码为:001,002的款项应收金额
        return;
    }

4.案例优化

如果我们遇到类似于上面的需求,第一反应肯定是用if-else语句或者switch语句,根据不同的情况执行不同的代码。但是随着需求越来越复杂,这么做的缺陷就慢慢的显现了出来:除了产品类型之外,如果增加了其他判断要素,如:收费周期、标的分类等,那么就会新增分支逻辑进行判断。

随着需求的不断增加,代码的分支会越来越多,代码最终会越来越难以维护,所以策略模式出现了。

4.1原始的策略模式

  • 先定义一个model:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Receivables {
    private String desc;
}
  • 定义计算策略的接口:
public interface CalculateService {
    List CalculateReceivables();
}
  • 定义不同的策略实现类,有CalculateServiceImpl1,CalculateServiceImpl2,CalculateServiceImpl3,CalculateServiceImpl4(这里只展示一个):
public class CalculateServiceImpl1 implements CalculateService {

    @Override
    public List CalculateReceivables() {
        List list = new ArrayList<>();
        list.add(new Receivables("产品1应收金额: 收费项001--金额1000.00"));
        list.add(new Receivables("产品1应收金额: 收费项002--金额2000.00"));
        return list;
    }
}
@Service
public class CalculateServiceImpl2 implements CalculateService {

    @Override
    public List CalculateReceivables() {
        List list = new ArrayList<>();
        list.add(new Receivables("产品2应收金额: 收费项001--金额2100.00"));
        list.add(new Receivables("产品2应收金额: 收费项002--金额6666.00"));
        return list;
    }
}
  • 在上下文中定义:产品类型 与 计算策略 的映射关系 --- 通过map去记录
    并提供 calculate( ) 接口,根据不同的产品类型,去调用不同的应收金额计算策略
public class CalculateStrategy {
    private static Map strategyMap = new HashMap<>();

    static {
        strategyMap.put(1, new CalculateServiceImpl1());
        strategyMap.put(2, new CalculateServiceImpl2());
        strategyMap.put(3, new CalculateServiceImpl3());
        strategyMap.put(4, new CalculateServiceImpl4());
    }

    public List calculate(Integer type) {
        List receivablesList = strategyMap.get(type).CalculateReceivables();
        List collect = receivablesList.stream()
                .map(Receivables::getDesc)
                .collect(Collectors.toList());
        return collect;
    }
}
  • controller层调用:
@RestController
@RequestMapping("/calculate")
@Slf4j
public class CalculateController {
    private static final Logger logger = LoggerFactory.getLogger(CalculateController.class);

    @Autowired
    private CalculateStrategy calculateStrategy;

    @GetMapping("/strategy-test")
    public void test(@RequestParam("type") String type) {
        List calculate = calculateStrategy.calculate(Integer.parseInt(type));
        calculate.forEach(logger::info);
    }
}
  • 运行结果:
2021-02-16 22:08:03.731  INFO 19176 --- [nio-8080-exec-1] c.q.s.controller.CalculateController     : 产品2应收金额: 收费项001--金额2100.00
2021-02-16 22:08:03.732  INFO 19176 --- [nio-8080-exec-1] c.q.s.controller.CalculateController     : 产品2应收金额: 收费项002--金额6666.00

4.2与spring结合的策略模式

4.2.1方式1

通过@Autowired + @PostConstruct 注入依赖,并将映射关系加到strategyMap中

@Service
public class CalculateStrategy {
    @Autowired
    private CalculateServiceImpl1 calculateServiceImpl1;

    @Autowired
    private CalculateServiceImpl2 calculateServiceImpl2;

    @Autowired
    private CalculateServiceImpl3 calculateServiceImpl3;

    @Autowired
    private CalculateServiceImpl4 calculateServiceImpl4;

    private static Map strategyMap = new HashMap<>();

    @PostConstruct
    public void init() {
        strategyMap.put(1, calculateServiceImpl1);
        strategyMap.put(2, calculateServiceImpl2);
        strategyMap.put(3, calculateServiceImpl3);
        strategyMap.put(4, calculateServiceImpl4);
    }

//    static {
//        strategyMap.put(1, calculateServiceImpl1);
//        strategyMap.put(2, calculateServiceImpl2);
//        strategyMap.put(3, calculateServiceImpl3);
//        strategyMap.put(4, calculateServiceImpl4);
//    }

    public List calculate(Integer type) {
        List receivablesList = strategyMap.get(type).CalculateReceivables();
        List collect = receivablesList.stream()
                .map(Receivables::getDesc)
                .collect(Collectors.toList());
        return collect;
    }
}

运行结果同4.1

4.2.2方式2

  • Spring有个技巧:自动注入Map类型

具体说明:直接通过Map 注入依赖,这里:beanName是map中的key,bean对应的接口(实现类)是map中的value

  • 首先,@Service注解标明beanName
@Service("2")
public class CalculateServiceImpl2 implements CalculateService {

    @Override
    public List CalculateReceivables() {
        List list = new ArrayList<>();
        list.add(new Receivables("产品2应收金额: 收费项001--金额2100.00"));
        list.add(new Receivables("产品2应收金额: 收费项002--金额6666.00"));
        return list;
    }
}

-其次,上下文中自动注入Map

@Service
public class CalculateStrategy {
    @Autowired
    private Map strategyMap;

    public List calculate(Integer type) {
        List receivablesList = strategyMap.get(String.valueOf(type)).CalculateReceivables();
        List collect = receivablesList.stream()
                .map(Receivables::getDesc)
                .collect(Collectors.toList());
        return collect;
    }
}

结果同4.1

5.小结

5.1优点

  • 策略的调用和具体策略的实现是松耦合关系。上下文只需要知道要使用哪一个实现Strategy接口的实例,但不需要知道具体是哪一个类。

    注:如果这些算法具有公共的功能,可以将接口变为抽象类,将公共功能放到抽象父类里面。

  • 策略模式满足“开闭原则”,当增加新的具体策略时,不需要修改上下文类的代码,就可以引用新的策略的实例。

5.2缺点

  • 增加了对象的数量
    由于策略模式将每个具体的算法都单独封装为一个策略类,如果可选的策略有很多的话,那对象的数量也会很多。
  • 只适合偏平的算法结构
    由于策略模式的各个策略实现是平等的关系(可相互替换),实际上就构成了一个扁平的算法结构。即一个策略接口下面有多个平等的策略实现,但是运行时只能有一个算法被使用。这就限制了算法的使用层级,且不能被嵌套。

5.3使用场景

  • 一个类定义了多种行为,并且这些行为在这个类的方法中以多个 if-else 条件语句的形式出现,那么使用策略模式避免在类中使用大量的条件语句。
  • 不希望暴露复杂的,与算法相关的数据结构,那么可以使用策略模式封装算法,即:将算法分别封装到具体策略实现类中。

你可能感兴趣的:(策略模式学习笔记)