简介
策略模式(Strategy Pattern)隶属于设计模式中的行为型模式,是日常开发中使用最广的一个模式,相对于其他模式,自认为这个模式是最容易理解和使用的。
模式定义
策略模式:定义一系列的算法,把他们封装起来,并使得他们可相互替换,使得算法可独立于使用的客户而变化。
策略模式中的策略在定义中被表述为算法,并不是只有我们在算法导论中学习到的才是算法,这里的算法是一个更广泛的含义,意为解决一个问题采用的步骤。在排序中,要解决的问题是对元素排序,快排、插排、冒泡排等是解决问题的不同策略;实际生活中,商场对如何对产品定价是一个问题,新老客户使用不同的折扣,大促与平常使用不同的折扣也是策略。
角色
策略模式有三个角色:上下文、抽象策略接口、具体策略实现。
策略模式中核心就是策略接口和策略实现,可以看到策略模式从本质上来说就是对继承和多态的使用,不同的策略实现之间也要遵循里氏替换原则,不过策略模式的不同策略实现之间还有其他约束,我们后面在看。
模式说明
各个网站现在都在上线会员服务,不同的会员在购买产品时所享受的价格是不一样的,会员会有不同的等级、积分,在很多服务上也会区分不同的会员,我们就以会员购买产品时可以使用的折扣来说明策略模式。
假设我们是电商网站,网站根据用户的积分分为正常用户,青铜会员,黄金会员,不同的会员在买产品时可以享受不同的折扣。如果不使用策略模式,那么对每一个类型的用户我们都需要使用 if else
来区分,面向过程的解决方法如下:
public class Website {
public static void main(String[] args) {
String memberType = args[0];
// 原价
float price = 100;
// 黄金会员打7 折
if ("1".equals(memberType)) {
price *= 0.7;
} else if ("2".equals(memberType)) {
// 青铜会员打八折
price *= 0.8;
} else {
// 正常用户打9折
price *= 0.9
}
//收费
System.out.println(price);
}
}
如果是再来一个钻石会员,那么就要修改网站里的这一堆if else 代码,违反了开闭原则,而且价格的计算细节直接放到网站中也不符合单一职责原则,因此我们使用策略模式来重写计算逻辑。
public interface Discount {
public float discount();
}
// 正常折扣
public class NormalDiscount implements Discount {
public float discount() {
return 0.9f;
}
}
//青铜折扣
public class BronzeDiscount implements Discount {
public float discount() {
return 0.8f;
}
}
// 黄金折扣
public class GoldDiscount implements Discount {
public float discount {
return 0.7f;
}
}
由于这里的策略模式比较简单,我们不写上下文对象,而是使用简单工厂模式来根据会员类型生成折扣策略实例。
public class SimpleDiscountFactory {
private static Map<String, Discount> discountMap = new HashMap<>();
static {
discountMap.put("1", new GoldDiscount());
discountMap.put("2", new BronzeDiscount());
}
public Discount getDiscount(String memeberType) {
Class<Discount> discount = discountMap.get(memberType);
if (discount != null) {
return discount;
}
// 默认返回正常折扣
return new NormalDiscount();
}
}
public class Website {
public static void main(String[] args) {
String memberType = args[0];
// 原价
float price = 100;
Discount discount = SimpleDiscountFactory.getDiscount(memberType);
price *= discount.discount();
//收费
System.out.println(price);
}
}
如果需要增加会员类型,那么只需要实现一个新的折扣类型就可以了,在这里我们只是简单的使用原价乘以折扣的方式来计算付费价格,日常中的计价计算会更复杂,可能不同的会员都会有一套计算逻辑,那么此时可以将计算价格的步骤抽象出来成为策略。策略的范围是由具体的问题复杂度来决定的。
使用场景
- 系统中有面对一个问题的多种解决方案
- 系统可以动态地选择解决方案,而无需变化代码
- 封装算法细节,实现对客户端使用方的隐藏
策略模式在日常中经常使用,有时可能我们都没有意识到,符合上面所说的三个条件的场景都可以使用策略模式,我们列一下日常具体的场景:
- 排序算法
- List、Map 的选择与实现
- 网站会员、等级、积分、折扣
- 。。。
一些要点
- 不同的算法之间是平等的。正因为策略模式中不同的策略是平等的,才可以实现无缝切换策略。
- 算法之间是独立的。不同的策略之间不要有依赖关系,易于后期对策略的替换。
- 策略模式在一时一般只有一个策略,对于上面的例子来说,对同一个用户只有一个策略。
- 策略模式在抽象出来之后,更重要的可能是客户端的程序如何组织才能使得程序更加具有灵活性,可以和其他设计模式配合使用来达到这个效果。
优点
- 策略实现之间可以直接替换,无需增加、修改代码
- 易于扩展,新增策略类非常容易
- 封装策略细节实现,客户端不需要了解具体策略的实现机制
- 易于维护和测试,策略类之间互不影响。
缺点
- 客户端虽然不需要知道每个策略的细节,但需要知道每个策略,这样才能选用对自身合适的策略。
- 设计模式的通用缺点:类增多。
与其他模式的配合使用
- 简单工厂模式,见上
- 模板方法模式。对于比较大的问题,解决方案通常有一套通用的解决路径,路径上不同的问题可以由不同的策略来实现。
- 享元模式。其实上面也使用了享元模式,只不过在这里没有外部状态和内部状态,是一个纯粹的函数是对象。
- 单例模式,每个策略可以实现为单例
最佳实践
- 使用枚举来表示策略
- 策略可以由外部配置或参数来决定,而无需直接写到代码中
- 使用注解等方式来更方便的使用策略