设计模式-策略模式

策略模式

策略模式是啥,就是选择,或者说是操作的选择。

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);
  }
}

嗯,自动注册。

判断

不过策略模式使用的场景还涉及一部分东西,那就是前置判断

这东西不算在策略模式的思想里面,但是不可否认的是,这种场景占据绝大多数。

从整体流程上来看,我们需要额外增加一个前置判断模块,然后再进行策略的处理获取。

Context
input
out
preProcess
postProcess
conditionA
conditionB
conditionC
otherCondition
condition
operationA
operationB
operationC
otherOperation
operation

也就是策略模式的前置判断步骤,而这个步骤刚好和所谓策略是相反的操作流程。

策略是细分,是发散,而判断是归纳,是总结。

两者通过一个条件结果进行关联,必要且唯一。

首先通过一个聚合的条件判断,去筛选出最适合的场景,然后以此选择具体的处理策略。


使用相同的思想去进行代码设计,大致包含三个部分

  • 条件注册中心
  • 策略注册中心
  • 条件策略绑定
context condition bind strategy 前置处理 条件匹配 查询处理策略 路由到策略 策略处理 后置处理 context condition bind strategy

后续三者都可以后期直接进行注入而不用修改代码,但是bind中的对应关系应该重点维护。

小结

很多模式很好用,但是局限也不小,只有多种模式的结合,以及特定场景的使用,才能达到目的。

尤其,容器这个东西真的很不错,至少从来不用去进行选择。使用的场景基本覆盖,很有用。

外观模式,也叫做门面模式,简而言之,就是不同的内涵,相同的马甲,后续详细说明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值