定义
定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化不会影响到使用算法的用户。
策略模式强调的是多个策略之间可以互换,这些策略是同一个接口的不同实现类。
模式结构
策略模式涉及三种角色
- 抽象策略角色(Strategy)
使用接口或抽象类实现,包含所有策略的公共行为规范。特别地,若公共行为以包含方法体的方式出现,而不是接口方法的方式,此时只能使用抽象类作为该角色的实现方式; - 环境角色(Context)
持有一个Strategy引用,供客户端调用; - 具体策略类(ConcreteStrategy)
实现抽象策略角色定义的公共行为规范;
三者的关系如下图
代码实践
假设某商城举办打折活动,分为满减、返现、和立减三种策略,根据不同的情况,分别执行不同的打折活动。策略模式的三种角色代码如下
-
环境角色
/** * @description: 促销活动--业务逻辑 * @Date: 2021/10/19 15:23 */ public class PromotionActivity { //持有策略接口引用 StrategyInterface strategyInterface; //通过构造器 初始化策略接口 public PromotionActivity(StrategyInterface strategyInterface){ this.strategyInterface = strategyInterface; } //促销活动自身的业务逻辑 public void executePromption(){ strategyInterface.doPromotion(); } }
-
抽象策略角色
//定义了执行打折的方法 public interface StrategyInterface { void doPromotion(); }
-
三个具体策略类
-
策略一
/** * @description: 返现促销-- 一种具体策略实现 * @Date: 2021/10/19 15:22 */ public class FanxianPromotionStrategy implements StrategyInterface{ @Override public void doPromotion() { System.out.println("返现促销"); } }
-
策略二
/** * @description: 立减策略-- 一种具体策略实现 * @Date: 2021/10/19 15:18 */ public class LijianPromitionStrategy implements StrategyInterface{ @Override public void doPromotion() { System.out.println("立减促销"); } }
-
策略三
/** * @description: 满减促销-- 一种具体策略实现 * @Date: 2021/10/19 15:21 */ public class ManjianPromitionStrategy implements StrategyInterface{ @Override public void doPromotion() { System.out.println("满减促销"); } }
完成以上代码编写之后,客户端即可基于环境对象,根据需要执行不同的折扣策略。对于环境角色来说,由于只依赖抽象,不依赖于具体实现。当有新的策略出现时,只需新增一个具体策略类,而不需要修改其他代码,体现了开闭原则。在本文中,当出现新的折扣活动时,只需新建一个实现StrategyInterface接口的具体折扣类,然后在客户端中调用即可,不需要修改其他代码代码。
/** * 创建环境角色对象,根据业务需要将具体策略对象传入环境角色构造器 */ static void baseTest(){ //使用立减折扣 PromotionActivity p618 = new PromotionActivity(new LijianPromitionStrategy()); p618.executePromption(); //使用返现折扣 PromotionActivity p1111 = new PromotionActivity(new FanxianPromotionStrategy()); p1111.executePromption(); }
-
策略模式+简单工厂模式消除if-else选择语句
在实际的工程实践中,可能将选择折扣方式封装为一个方法,代码如下
/**
* 单独使用策略模式
* 根据传入的策略key,返回对应的策略模式
* 这种方式未消除if-else语句,但产生了多个类 扬短避长
* @param promotionKey
*/
static PromotionActivity useStrategy(String promotionKey){
PromotionActivity p = null;
if("Lijian".equals(promotionKey)){
p = new PromotionActivity(new LijianPromitionStrategy());
}else if("Fanxian".equals(promotionKey)){
p = new PromotionActivity(new FanxianPromotionStrategy());
}else if("Manjian".equals(promotionKey)){
p = new PromotionActivity(new ManjianPromitionStrategy());
}else{
//promotionKey == null时,返回一个空折扣类;
// 使程序更加鲁棒,用户体验更好
//良好的编程习惯
p = new PromotionActivity(new EmptyPromotionStrategy());
}
return p;
}
客户端调用useStrategy方法,并传入折扣关键字,方法内部根据折扣关键字选择创建对应的折扣策略类。当折扣策略较少且变化较少时,可使用该方式。但若折扣策略经常发生变化,则需要不断的添加if-else选择语句,最终造成该方法的代码块十分臃肿。此时可结合简单工厂模式,消除if-else语句块,简化代码。
- 创建策略简单工厂
简单工厂主要用于存储折扣策略关键字和对应的折扣策略类,并向客户端提供获取策略的方法。代码如下/** * @description: 生产策略的简单工厂 * @Date: 2021/10/20 10:57 */ public class StrategySimpleFactory { private static Map<String,StrategyInterface> PROMOTION_MAP = new HashMap<>(); //存储折扣策略关键字和对应折扣实例 static { PROMOTION_MAP.put("Lijian",new LijianPromitionStrategy()); PROMOTION_MAP.put("Manjian",new ManjianPromitionStrategy()); PROMOTION_MAP.put("Fanxian",new FanxianPromotionStrategy()); } //封装null为对象 private static final StrategyInterface EMPTY_STRATEGY = new EmptyPromotionStrategy(); //私有构造器,不让外部创建该工厂 private StrategySimpleFactory(){}; //供客户端使用 public static StrategyInterface getStrategy(String promotionKey){ StrategyInterface si = PROMOTION_MAP.get(promotionKey); //return si == null ? new EmptyPromotionStrategy() : si; return si == null ? EMPTY_STRATEGY : si; } }
- 客户端调用策略
static void useStategyWithSimpleFactory(String promotionKey){ PromotionActivity promotionActivity = new PromotionActivity( StrategySimpleFactory.getStrategy(promotionKey)); promotionActivity.executePromption(); }
从以上代码可以看到,客户端调用时不必再无限度的扩展if-else语句。当有新策略时,只需创建新的策略类,并在简单工厂类中的PROMOTION_MAP存入对应的k-v值即可。
需要注意的时,策略模式提供的是一组可互换位置的策略,而对于具体选择哪种策略,需要客户端自行选择。使用if-else语句是一种选择策略的直接方式,策略模式+简单工厂模式也是一种选择策略的方式,这两种方式都需要客户端提供策略选择的依据(策略实例对应的key),区别在于新增策略时,if-else语句需要直接在客户端中修改代码,而策略模式+简单工厂模式只需在简单工厂模式的PROMOTION_MAP中添加一项数据即可,后者进一步封装了变化的部分,代码也更整洁。
源码分析
java的外部比较器使用了策略模式,具体参看java的两种比较器
适用场景
策略模式强调了策略的互换,因此其适用场景应可以使用该特点。
- 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时,将具体行为抽象为策略;
- 需要安全地封装多种同一类型的操作时,将同一类型抽象为策略;
- 出现同一抽象类有多个子类,而又需要使用if-else或者switch-case来选择具体子类时。这种情况可配合简单工程模式,去除if-else的判断语句。