策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的主要优点如下:
主要缺点如下:
策略模式涉及三个角色:
提到策略模式不得不说一下《三十六计》,它是根据中国古代军事思想和丰富的斗争经验总结而成的兵书,是中华名族悠久非物质文化遗产之一,它的身影在留下来的战争故事中无处不在。
知己知彼方能百战不殆,在战争中使用哪种谋略需要因人而异。张三好色,使用美人计获取情报,而后图之;李四狡诈,使用苦肉计使其麻痹,放下戒心,然后破之;王二与麻子有仇,只需坐山观虎斗,使用借刀杀人让麻子杀了王二即可。
这个场景可以套用策略模式来实现
首先定义所有计策的抽象类,所有策略的目的都是为了击败对手,定义公共方法 fightEnemy
/** * 策略接口,定义所有的接口 * @date 2019/5/22 9:50 */public interface FightStrategy { /** * 杀敌之法 */ public void fightEnemy();}
我们定义三种策略,分别是美人计,苦肉计,借刀杀人计:
/** * 三十六计之美人计 */public class HoneyTrapStrategy implements FightStrategy{ @Override public void fightEnemy() { System.out.println("使用‘美人计’取得胜利"); }}
/** * 三十六计之苦肉计 */public class SelfInjuryStrategy implements FightStrategy {
@Override public void fightEnemy() { System.out.println("使用’苦肉计‘取得胜利"); }}
/** * 三十六计之借刀杀人 */public class CollateralStrategy implements FightStrategy{ @Override public void fightEnemy() { System.out.println("使用’借刀杀人‘取得胜利"); }}
环境角色主要是持有一个具体的策略,我们使用构造器在初始化环境类时传入具体的策略
/** * 环境角色-持有具体策略的引用 */public class StrategyContext { private FightStrategy strategy;
public StrategyContext(FightStrategy strategy) { this.strategy = strategy; }
public void fight(){ this.strategy.fightEnemy(); }}
/** * 客户端需要根据具体的对手选择具体的策略 */public class FightClient { public static void main(String[] args) { FightClient client = new FightClient(); client.fightEnemy("李四"); }
private void fightEnemy(String enemyName) { StrategyContext context = null; switch (enemyName){ case "张三" : context = new StrategyContext(new HoneyTrapStrategy()); break; case "李四": context = new StrategyContext(new SelfInjuryStrategy()); break; case "王二": context = new StrategyContext(new CollateralStrategy()); break; }
context.fight(); }}
在上面例子中客户端需要承担根据敌人选择具体的策略职责,即上面的 case
语句的实现逻辑,把这样一大段代码放在客户端会造成客户端臃肿,影响阅读体验,我们有2种优化策略:
使用简单工厂方法,将选择策略的判断逻辑抽取到工厂类中,客户端传入 enemyName
给简单工厂生成具体策略,实现逻辑如下:
/** * 简单工厂方法 * 根据敌人名称选择具体的策略 */public class StrategyFactory { public static FightStrategy createFightStrategy(String enemyName){ FightStrategy strategy; switch (enemyName){ case "张三" : strategy = new HoneyTrapStrategy(); break; case "李四": strategy = new SelfInjuryStrategy(); break; case "王二": strategy = new CollateralStrategy(); break; default: throw new IllegalStateException("Unexpected value: " + enemyName); } return strategy; }}
接下来改造客户端,选择具体策略的方法使用简单工厂生成:
/** * 使用简单工厂构建具体的策略 */public class FightClient { public static void main(String[] args) { FightClient client = new FightClient(); client.fightEnemy("张三"); }
private void fightEnemy(String enemyName) { FightStrategy strategy = StrategyFactory.createFightStrategy(enemyName); StrategyContext context = new StrategyContext(strategy); context.fight(); }}
这里主要改造环境角色类,构造方法不再接收具体的策略对象,而是使用 enemyName
作为参数接收,让其拥有根据 enemyName
选择策略的能力,改造后的环境类如下:
/** * 环境角色 结合简单工厂选择具体的策略 */public class StrategyContext {
private FightStrategy strategy;
public StrategyContext(String enemyName) { switch (enemyName){ case "张三" : strategy = new HoneyTrapStrategy(); break; case "李四": strategy = new SelfInjuryStrategy(); break; case "王二": strategy = new CollateralStrategy(); break; default: throw new IllegalStateException("Unexpected value: " + enemyName); } }
public void fight(){ this.strategy.fightEnemy(); }}
改造后的客户端代码如下:
/** * 环境角色拥有选择策略的能力,客户端只需要认识Context角色 */public class FightClient { public static void main(String[] args) { FightClient client = new FightClient(); client.fightEnemy("王二"); }
private void fightEnemy(String enemyName) { StrategyContext context = new StrategyContext(enemyName); context.fight(); }}
使用简单工厂方法时客户端需要认识两个类: FightStrategy
, StrategyContext
,而使用策略与简单工厂结合的方式客户端只需要认识 StrategyContext
即可,这使得算法类彻底与客户端分离,耦合度会更低。
策略模式的使用场景很多,主要有以下几类: