今天回顾下策略模式的相关知识点。之前看网上写的一些博文,大多都说策略模式是设计模式中相对比较简单的模式了,不可否认,它确实比较简单容易理解,但是如果想要在业务代码中用起来得心应手那还是不简单的,今天,就来剖析下策略模式在实际应用中的使用姿势。 第一步,先定义出自己的策略模式,这里我用的是接口抽象,当然用抽象类也是ok的,无伤大雅
public interface StrategyService {
void process();
}
简简单单,就一个方法,用来写后续具体实现类的相关策略逻辑,下一步就是定义策略,也就是实现具体的类,这里就举例两个实现类,多了容易混淆视听,在实际业务中肯定不止两个实现类,如果是两个的话建议还是直接用if判断就好了,没有必要去硬用策略模式了
@Service
public class StrategyA implements StrategyService {
@Override
public void process() {
System.out.println("执行策略A逻辑");
}
}
@Service
public class StrategyB implements StrategyService {
@Override
public void process() {
System.out.println("执行策略B逻辑");
}
}
为了演示效果,简简单单打印下用来区分策略的不同,当然也是为了方便理解,因为只有彻底理解了,才能运用自如。
到这里,策略模式的策略部分基本就是这样的结构。在传统的策略模式中,还有一个Context的上下文结构,这里我们也把它写出来
public class Context {
private StrategyService strategyService;
public Context(StrategyService strategyService) {
this.strategyService = strategyService;
}
public void process() {
strategyService.process();
}
}
这个Context的作用其实就是根据传进来的具体实现类来调用上面的策略,其实这里用到一个知识点,就是里式替换原则,六大设计原则之一,简单来说就是父类出现的地方都可以使用子类来替代,这应该是设计模式里用到最多的原则了
完整的策略模式骨架基本就是这样了,接下来是重头戏,就是在业务方如何使用的问题,这里,我介绍5种使用方式,其中一种是用原生的Context的方法,接下来就一个一个来实现这四种方式,首先是第一种原生Context的方式
@RestController
public class TestController {
@PostMapping("/test")
public void test(@RequestParam String key) {
StrategyService strategyService=null ;
if(key.equals("StrategyA") ){
strategyService=new StrategyA();
}else {
strategyService=new StrategyB();
}
Context context = new Context(strategyService);
context.process();
}
}
Context的方式是基于在调用方判断使用哪种策略来实现的,这种方式的优点就是直接、代码直观,简单易懂,缺点嘛,最直观的就是有不太舒服的if else,本来策略模式的作用是为了消除if的,这里只有两个策略还可以接受,如果后续持续增加策略的话,那if的数量就会持续增加,到时候代码的观赏性也就大打折扣了,有人可能会说用Switch,其实差不多,只能说比if好看了那么一丢丢而已,虽然有这么多缺点,但是实现功能是完全没有问题,跑起来看看结果
可以看到执行的结果是我们预期的,接下来是第二种,现在有了springboot,bean都放在springboot的上下文中了,还需要自己实现Context吗
@RestController
public class Commont {
@Resource
private final Map<String,StrategyService> strategyServiceMap=new ConcurrentHashMap<>();
@PostMapping("/test1")
public void test1(@RequestParam String key) {
StrategyService strategyService = strategyServiceMap.get(key);
strategyService.process();
}
直接让springboot给我们获取所有的策略Map,然后通过key来获取到具体是实现类,和上面对比优点显而易见,调用代码中成功消除了if else的冗余代码,代码更加简洁了,而且后续新增策略,在调用方这里无需更改代码,对调用方而言就是无感,直接屏蔽了策略的细节,至于缺点嘛,就是这个key,它是固定的,取的是实现类的全称,没有办法修改,但是这个类名实际上在业务中是没有实际含义的,也就是说在业务参数的基础上新增这么一个来调用策略的字符串,还是有点不舒服的,那么下面通过第三种来优化这个缺点,首先在接口中需要增加个方法
public interface StrategyService {
String getKey();
}
然后具体策略都实现它
@Service
public class StrategyA implements StrategyService {
@Override
public String getKey() {
return "A";
}
}
@Service
public class StrategyB implements StrategyService {
@Override
public String getKey() {
return "B";
}
}
像这样,这里每个策略实现类中返回的可以定义相关业务的枚举或者参数的值,然后调用的时候,使用
ApplicationContextAware,来实现一下
@RestController
public class Commont implements ApplicationContextAware {
private Map<String, StrategyService> serviceMap = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, StrategyService> beansOfType = applicationContext.getBeansOfType(StrategyService.class);
beansOfType.forEach((k, v) -> {
serviceMap.put(v.getKey(), v);
});
}
@PostMapping("/test2")
public void test2(String key) {
StrategyService strategyService = serviceMap.get(key);
strategyService.process();
}
首先解释下ApplicationContextAware吧,它其实和@Resource差不多,唯一的区别就是可以通过setApplicationContext方法来定义或者修改applicationContext上下文管理的bean,比如名称,像上面,就通过它来把所有的策略实现类put进来自己定义的map中,这样,在使用的时候就可以通过业务参数获取对应的策略实现类,紧贴业务,后续的扩展也就轻松自如了,但是这个方法最大的缺点就是每个调用的地方都需要实现这么一个ApplicationContextAware,如果调用的地方很多的话,那么冗余的代码就会有很多,是时候搬出第四种实现方式了
这种方式是通过@Service的特性来的,它有一个属性,可以设置类的别名,通过这个特性可以把类的别名设置为对应的业务参数的值,然后通过ApplicationContext别名获取对应的bean,可以实现同样的效果
@RestController
public class Commont {
@Resource
private ApplicationContext applicationContext;
@PostMapping("/test3")
public void test3(@RequestParam String key) {
StrategyService bean = null;
try {
bean = (StrategyService) applicationContext.getBean(key);
} catch (Exception e) {
System.out.println("不支持该业务");
}
bean.process();
}
}
首先需要注入一个ApplicationContext,然后通过getBean方法根据key获取具体的策略类,不要忘了把对应的策略类修改下
@Service("A")
public class StrategyA implements StrategyService {
@Override
public String getKey() {
return "StrategyA";
}
@Override
public void process() {
System.out.println("执行策略A逻辑");
}
}
@Service(value = "B")
public class StrategyB implements StrategyService {
@Override
public String getKey() {
return "StrategyB";
}
@Override
public void process() {
System.out.println("执行策略B逻辑");
}
}
只修改@Service中的值,分别设置为A和B,跑起来看看效果
看起来想要的效果完全可以实现,在使用的时候只需要使用ApplicationContext即可,完全不需要使用
ApplicationContextAware那样设置自己的map了,但是有人会说Service改成A、B这样完全没有业务含义,而且看起来很怪,那你要是这么抬杠的话,倒是也有办法,大不了就再包一层呗,再定义个常量管理类
public class StrategyString {
public static final String STRATEGYA = "A";
public static final String STRATEGYB = "B";
}
@Service(StrategyString.STRATEGYA)
public class StrategyA implements StrategyService {
@Override
public String getKey() {
return "StrategyA";
}
@Override
public void process() {
System.out.println("执行策略A逻辑");
}
}
这样是不是可以了,杠精看着是不是舒服了,就去用吧,一用一个不吱声。
到这里,策略模式算是全部完成了,我想弄完这些大概会有一些新的感悟,在业务中使用起来也会更顺手。
最后,还是要送上一位名人曾说的一句话:手上没有剑和有剑不用是两回事!