策略模式
策略模式是啥,就是选择
,或者说是操作的选择。
def calc(operation, a, b):
if operation == "add":
return a + b
elif operation == "sub":
return a - b
elif operation == "mul":
return a * b
elif operation == "div":
return a / v
raise Exception(f'unknow operation : {operation}')
这是最简单的例子,写法上没有问题,但是实际场景下面,这几点不得不说
- 判断冗余
太多的分支判断,判断参数被重复的使用。
- 操作复杂
很多情况下,操作并不是简单的一两句话,如果全部陈列,代码不可谓优美。
- 维护性差
如果想要增加新的选项,居然还要去改动原来的代码。
- …
基于以上原因,总结规律之后发现,我们可以这样做
- 操作包装
首先,把操作进行包装成方法,方便调用。
- 直接选择
避免重复条件对比,直接指定operation
,而非逐个进行对比,采用hash
直接选取操作。
- 动态注册
这个就是额外的点了,本身并不包含在策略模式之内,但是如果有个归纳的容器, 更好的管理就更好了。
初始问题
def calc(a, b):
return a + b
最初情况下,计算的场景只是包含add
。
这种场景下,新增计算方式,不仅要新增代码,还需要修改其他计算无关部分,甚至项目结构。
于是,变成了最上面的方式。
不过问题还在,还是需要去修改原来的代码,即使每个分支的操作封装成了方法。
同时,也还算不上是策略模式,策略模式最关键的在于
相同的概念,各种具体方式的选择。
也就是说,虽然叫做calc
,但是具体操作的时候,我必定明确了到底是哪一种操作。
这种直接遍历,说实话,太糟糕。
策略实现
def add(a, b):
return a + b
def div(a, b):
if b == 0:
raise Exception("div by zero")
return a / b
def calc(operation, a, b):
return operation(a, b)
calc(add, a, b)
这样一来,的确称得上是策略模式
了,或者通俗一些,应该称作内核替换
。
不管是交通、吃饭还是什么,它确切的存在一个场景,一个概念,但是落实的时候,具象化的现实是独一无二的。
所以具体的就会知道交通是火车、汽车还是轮船,饭是老干妈鸡蛋还是老干妈鸡蛋还是老干妈鸡蛋。
在关键处留白,留下一个占位,简历流程上的完整性,但是忽略具体的操作,仅仅留下一个可操作
方法。
通过外部去指定,去传入这个方法,从而达到流程化中的细节替换。
我们可以搭建一条流水线去解决一类的问题,而不能为了解决一个问题而搭建一条流水线。
通过这种方式,可以保证一类操作流程上的相似性,而差异化的部分通过补丁
来进行修正。
标准实现
接口
public interface CalcStrategy{
public double calc(double a, double b);
}
场景(上下文)
public class Context{
double a;
double b;
double result;
public void setParams(double a, double b){
this.a = a;
this.b = b;
}
private void before(){
System.out.printf("a = %s, b = %s\n", a, b);
}
private void after(){
System.out.printf("result = %s \n", result)
}
public void dealWith(CalcStrategy calcStragegt){
before();
result = calcStragety(a, b);
alter(result);
}
}
策略
public class AddStrategy implement CalcStrategy{
public double calc(double a, double b){
return a + b;
}
}
public class SubStrategy implement CalcStrategy{
public double calc(double a, double b){
return a - b;
}
}
使用
public static void main(String [] args){
Context ctx = Context();
ctx.setParams(2, 1);
ctx.dealWith(new AddStrategy());
}
注册
策略模式本身有个天然的缺点:默认你已经知道将要进行的具体操作。
这样一来,它的功用就是把原来复杂的为了进行逻辑兼容的分支。
对比一下工厂模式,你会发现两者都是一样的,就是既定场景的默认。
不过一个针对的操作,而一个针对的是对象。
同理,我们都可以使用外观模式
进行统一。
public abstract AbstractCalcStrategy implements CalcStrategy{
private final ConcurrentHashMap<String, CalcStrategy> calcStrategies = new ConcurrentHashMap<>();
@PostConstruct
public void init(){
calcStragegies.put(getClass().getName(), this);
}
public CalcStrategy getStrategy(Class<? extends CalcStrategy> clazz){
String name = clazz.getName();
if (! calcStragegies.containsKey(name)){
return null;
}
return strategies.get(name);
}
}
嗯,自动注册。
判断
不过策略模式使用的场景还涉及一部分东西,那就是前置判断。
这东西不算在策略模式的思想里面,但是不可否认的是,这种场景占据绝大多数。
从整体流程上来看,我们需要额外增加一个前置判断模块,然后再进行策略的处理获取。
也就是策略模式的前置判断步骤,而这个步骤刚好和所谓策略是相反的操作流程。
策略是细分,是发散,而判断是归纳,是总结。
两者通过一个条件结果进行关联,必要且唯一。
首先通过一个聚合的条件判断,去筛选出最适合的场景,然后以此选择具体的处理策略。
使用相同的思想去进行代码设计,大致包含三个部分
- 条件注册中心
- 策略注册中心
- 条件策略绑定
后续三者都可以后期直接进行注入而不用修改代码,但是bind
中的对应关系应该重点维护。
小结
很多模式很好用,但是局限也不小,只有多种模式的结合,以及特定场景的使用,才能达到目的。
尤其,容器
这个东西真的很不错,至少从来不用去进行选择。使用的场景基本覆盖,很有用。
外观模式,也叫做门面模式,简而言之,就是不同的内涵,相同的马甲,后续详细说明。