策略模式--避免冗长的if/else

算是读书笔记吧

极客时间--设计模式之美


什么是策略模式

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)

策略模式对策略的定义、创建、使用三部分进行了解耦


策略模式的作用

策略模式解决的问题

策略模式表面上看是为了避免 if-else 分支判断逻辑,但更深层次上,还是为了解耦以及控制代码复杂度

比如在出门旅游时:路线、交通工具的类型、天数、舱位等级、餐饮、住宿等等。
每个节点在执行时,都需要根据预算Type进行不同的操作,从而引起大量的if-else判断。增加一个策略,修改一个策略,都有可能牵一发而动全身。需要对所有状态进行回测。

public void departure () {
  Int money = 100;
  String destination = getDestination(money); //获取目的地
  String vehicles = getVehicles(money); //获取交通工具
  天数、舱位等级、餐饮、住宿等等.....
}

private String getDestination (Int money) {
  if (num < 1000) {
    return "1000块能去得起的地方";
  } else if (num < 5000) {
    return "5000块能去得起的地方";
  } else if (num < 10000) {
    return "10000块能去得起的地方";
  } else if (num < 20000) {
    return "20000块能去得起的地方";
  } else if (num < 40000) {
    return "40000块能去得起的地方";
  } else if (num < 80000) {
    return "80000块能去得起的地方";
  } else  ...
}

private String getVehicles (Int money) {
  if (num < 1000) {
    return "骑车";
  } else if (num < 5000) {
    return "火车";
  } else if (num < 10000) {
    return "火车";
  } else if (num < 20000) {
    return "飞机";
  } else if (num < 40000) {
    return "飞机";
  } else if (num < 80000) {
    return "飞机";
  } else  ...
}

...后面还有一大堆相关的方法需要判断

整个业务如图所示,所有的判断都耦合在业务流程内部,牵一发而动全身

使用策略模式

我们可以将某一条件(Type)下的逻辑,聚合封装到具体的策略类中

public class departureStrategy1000 implements Strategy {  //1000块的旅行策略
  @Override
  public void  getDestination() {
    return "1000块能去得起的地方";
  }
  public void  getVehicles() {
    return "骑车";
  }
  天数、舱位等级、餐饮、住宿等等.....
}

使用策略类后如图所示,每个的情况被封装聚合到单个策略类中,相互隔离


所以策略模式的作用主要体现在:

  1. 解耦策略的定义、创建和使用
    控制代码的复杂度,让每个部分都不至于过于复杂、代码量过多。
  2. 让复杂框架满足开闭原则
    添加或者修改新策略的时候,最小化、集中化代码改动,减少引入 bug 的风险。

策略的定义

策略的定义包含一个策略接口和一组实现这个接口的策略类。
利用基于接口而非实现编程的方式,对具体策略进行解耦。

如下,策略类ConcreteStrategyA、ConcreteStrategyB在策略接口algorithmInterface的使用上,可以随意替换。

public interface Strategy {  //定义策略接口
  void algorithmInterface();
}

public class ConcreteStrategyA implements Strategy {  //实现策略接口的策略类A
  @Override
  public void  algorithmInterface() {
    //具体的算法...
  }
}

public class ConcreteStrategyB implements Strategy { //实现策略接口的策略类B
  @Override
  public void  algorithmInterface() {
    //具体的算法...
  }
}

策略的创建

通常会通过类型(type)来判断创建哪个策略来使用。

这里,有两种创建方式

if-else创建

适用有状态的策略类,每次创建一个新的策略类给业务方使用


public class StrategyFactory {
  public static Strategy getStrategy(String type) {
    if (type == null || type.isEmpty()) {
      throw new IllegalArgumentException("type should not be empty.");
    }

    if (type.equals("A")) {
      return new ConcreteStrategyA();
    } else if (type.equals("B")) {
      return new ConcreteStrategyB();
    }

    return null;
  }
}

通过工厂模式里的Map进行创建

适用于无状态的策略类创建,大家共用一个策略类即可

public class StrategyFactory {
  private static final Map<String, Strategy> strategies = new HashMap<>();

  static {
    strategies.put("A", new ConcreteStrategyA());
    strategies.put("B", new ConcreteStrategyB());
  }

  public static Strategy getStrategy(String type) {
    if (type == null || type.isEmpty()) {
      throw new IllegalArgumentException("type should not be empty.");
    }
    return strategies.get(type);
  }
}

本质上点讲,是借助“查表法”,根据 type 查表替代根据 type 分支判断。

有状态的策略类如何用Map进行创建

可以利用闭包的特性,将创建的逻辑封装进callback中,然后将callback存进Map


策略类的使用

如果使用工厂方法创建策略类,其实就和工厂方法相同。
只不过我们从工厂取出来的不再是一个某一个具体类的子类簇。
而是一个实现了策略接口的类簇。

// 运行时动态确定,根据配置文件的配置决定使用哪种策略
public class Application {
  public static void main(String[] args) throws Exception {
    Strategy strategy = null;
    StrategyFactory factory = new StrategyFactory(); 
    strategy = factory.getStrategy("A"); //获取策略类
    strategy.algorithmInterface(); //调用策略接口
    //...
  }
}

是不是要抹杀所有的if/else

如果 if-else 分支判断不复杂、代码不多,这并没有任何问题,毕竟 if-else 分支判断几乎是所有编程语言都会提供的语法,存在即有理由。遵循 KISS 原则,怎么简单怎么来,就是最好的设计。非得用策略模式,搞出 n 多类,反倒是一种过度设计

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容