定义与特点

策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

策略模式的主要优点如下:

  • 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。

  • 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。

  • 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。

  • 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

主要缺点如下:

  • 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。

  • 策略模式造成很多的策略类。

UML

CRUD很无聊?一起学设计模式吧!--策略模式-LMLPHP

角色定义

策略模式涉及三个角色:

  • 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色定义所有支持的算法的公共接口。

  • 具体策略(ConcreteStrategy)角色,封装了具体的算法或行为。

  • 环境(Context)角色:用一个ConcreteStrategy来配置,持有一个Strategy的引用。

场景实战

提到策略模式不得不说一下《三十六计》,它是根据中国古代军事思想和丰富的斗争经验总结而成的兵书,是中华名族悠久非物质文化遗产之一,它的身影在留下来的战争故事中无处不在。

知己知彼方能百战不殆,在战争中使用哪种谋略需要因人而异。张三好色,使用美人计获取情报,而后图之;李四狡诈,使用苦肉计使其麻痹,放下戒心,然后破之;王二与麻子有仇,只需坐山观虎斗,使用借刀杀人让麻子杀了王二即可。

这个场景可以套用策略模式来实现

代码示例

抽象策略

首先定义所有计策的抽象类,所有策略的目的都是为了击败对手,定义公共方法 fightEnemy

  
  1. /**

  2. * 策略接口,定义所有的接口

  3. * @date 2019/5/22 9:50

  4. */

  5. public interface FightStrategy {

  6. /**

  7. * 杀敌之法

  8. */

  9. public void fightEnemy();

  10. }

具体策略

我们定义三种策略,分别是美人计,苦肉计,借刀杀人计:

  
  1. /**

  2. * 三十六计之美人计

  3. */

  4. public class HoneyTrapStrategy implements FightStrategy{

  5. @Override

  6. public void fightEnemy() {

  7. System.out.println("使用‘美人计’取得胜利");

  8. }

  9. }


  
  1. /**

  2. * 三十六计之苦肉计

  3. */

  4. public class SelfInjuryStrategy implements FightStrategy {


  5. @Override

  6. public void fightEnemy() {

  7. System.out.println("使用’苦肉计‘取得胜利");

  8. }

  9. }


  
  1. /**

  2. * 三十六计之借刀杀人

  3. */

  4. public class CollateralStrategy implements FightStrategy{

  5. @Override

  6. public void fightEnemy() {

  7. System.out.println("使用’借刀杀人‘取得胜利");

  8. }

  9. }

环境角色

环境角色主要是持有一个具体的策略,我们使用构造器在初始化环境类时传入具体的策略

  
  1. /**

  2. * 环境角色-持有具体策略的引用

  3. */

  4. public class StrategyContext {

  5. private FightStrategy strategy;


  6. public StrategyContext(FightStrategy strategy) {

  7. this.strategy = strategy;

  8. }


  9. public void fight(){

  10. this.strategy.fightEnemy();

  11. }

  12. }

客户端

  
  1. /**

  2. * 客户端需要根据具体的对手选择具体的策略

  3. */

  4. public class FightClient {

  5. public static void main(String[] args) {

  6. FightClient client = new FightClient();

  7. client.fightEnemy("李四");

  8. }


  9. private void fightEnemy(String enemyName) {

  10. StrategyContext context = null;

  11. switch (enemyName){

  12. case "张三" :

  13. context = new StrategyContext(new HoneyTrapStrategy());

  14. break;

  15. case "李四":

  16. context = new StrategyContext(new SelfInjuryStrategy());

  17. break;

  18. case "王二":

  19. context = new StrategyContext(new CollateralStrategy());

  20. break;

  21. }


  22. context.fight();

  23. }

  24. }

执行结果

CRUD很无聊?一起学设计模式吧!--策略模式-LMLPHP

扩展

在上面例子中客户端需要承担根据敌人选择具体的策略职责,即上面的 case语句的实现逻辑,把这样一大段代码放在客户端会造成客户端臃肿,影响阅读体验,我们有2种优化策略:

简单工厂

使用简单工厂方法,将选择策略的判断逻辑抽取到工厂类中,客户端传入 enemyName给简单工厂生成具体策略,实现逻辑如下:

  
  1. /**

  2. * 简单工厂方法

  3. * 根据敌人名称选择具体的策略

  4. */

  5. public class StrategyFactory {

  6. public static FightStrategy createFightStrategy(String enemyName){

  7. FightStrategy strategy;

  8. switch (enemyName){

  9. case "张三" :

  10. strategy = new HoneyTrapStrategy();

  11. break;

  12. case "李四":

  13. strategy = new SelfInjuryStrategy();

  14. break;

  15. case "王二":

  16. strategy = new CollateralStrategy();

  17. break;

  18. default:

  19. throw new IllegalStateException("Unexpected value: " + enemyName);

  20. }

  21. return strategy;

  22. }

  23. }

接下来改造客户端,选择具体策略的方法使用简单工厂生成:

  
  1. /**

  2. * 使用简单工厂构建具体的策略

  3. */

  4. public class FightClient {

  5. public static void main(String[] args) {

  6. FightClient client = new FightClient();

  7. client.fightEnemy("张三");

  8. }


  9. private void fightEnemy(String enemyName) {

  10. FightStrategy strategy = StrategyFactory.createFightStrategy(enemyName);

  11. StrategyContext context = new StrategyContext(strategy);

  12. context.fight();

  13. }

  14. }

策略与简单工厂结合

这里主要改造环境角色类,构造方法不再接收具体的策略对象,而是使用 enemyName作为参数接收,让其拥有根据 enemyName选择策略的能力,改造后的环境类如下:

  
  1. /**

  2. * 环境角色 结合简单工厂选择具体的策略

  3. */

  4. public class StrategyContext {


  5. private FightStrategy strategy;


  6. public StrategyContext(String enemyName) {

  7. switch (enemyName){

  8. case "张三" :

  9. strategy = new HoneyTrapStrategy();

  10. break;

  11. case "李四":

  12. strategy = new SelfInjuryStrategy();

  13. break;

  14. case "王二":

  15. strategy = new CollateralStrategy();

  16. break;

  17. default:

  18. throw new IllegalStateException("Unexpected value: " + enemyName);

  19. }

  20. }


  21. public void fight(){

  22. this.strategy.fightEnemy();

  23. }

  24. }

改造后的客户端代码如下:

  
  1. /**

  2. * 环境角色拥有选择策略的能力,客户端只需要认识Context角色

  3. */

  4. public class FightClient {

  5. public static void main(String[] args) {

  6. FightClient client = new FightClient();

  7. client.fightEnemy("王二");

  8. }


  9. private void fightEnemy(String enemyName) {

  10. StrategyContext context = new StrategyContext(enemyName);

  11. context.fight();

  12. }

  13. }

对比

使用简单工厂方法时客户端需要认识两个类: FightStrategy, StrategyContext,而使用策略与简单工厂结合的方式客户端只需要认识 StrategyContext即可,这使得算法类彻底与客户端分离,耦合度会更低。

应用场景

策略模式的使用场景很多,主要有以下几类:

  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。

  • 《重构》一书中指出策略模式可以作为优化条件语句的技巧,一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。

  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。

  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。

  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

相关阅读

CRUD很无聊?一起学设计模式吧!— 观察者模式

CRUD很无聊?一起学设计模式吧!— 命令模式


CRUD很无聊?一起学设计模式吧!--策略模式-LMLPHP

本文分享自微信公众号 - JAVA日知录(javadaily)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

09-11 12:20
查看更多