策略模式(Strategy Pattern)
背景
在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法。
在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。
如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题。
基本介绍
定义
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式对应于解决某一个问题的一个算法族,允许用户从该算法族中任选一个算法解决某一问题,同时可以方便的更换算法或者增加新的算法。并且由客户端决定调用哪个算法。
本质
分离算法,选择实现。
模式分析
优点
- 多重条件语句(if…else if…else)不易维护,而使用策略模式可以避免使用多重条件语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
缺点
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大。
注意
- 策略模式的关键是:分析项目中变化部分与不变部分
- 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性
模式结构
UML类图
结构分析
- 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
代码实现
抽象策略类
interface Strategy
{
//策略
public void strategyMethod(); 方法
}
具体策略类
具体策略类A
class ConcreteStrategyA implements Strategy
{
public void strategyMethod()
{
System.out.println("具体策略A的策略方法被访问!");
}
}
具体策略类B
class ConcreteStrategyB implements Strategy
{
public void strategyMethod()
{
System.out.println("具体策略B的策略方法被访问!");
}
}
环境类
聚合抽象策略类
class Context
{
private Strategy strategy;
public Strategy getStrategy()
{
return strategy;
}
public void setStrategy(Strategy strategy)
{
this.strategy=strategy;
}
public void strategyMethod()
{
strategy.strategyMethod();
}
}
客户端调用
public class StrategyPattern
{
public static void main(String[] args)
{
Context c=new Context();
Strategy s=new ConcreteStrategyA();
c.setStrategy(s);
c.strategyMethod();
System.out.println("-----------------");
s=new ConcreteStrategyB();
c.setStrategy(s);
c.strategyMethod();
}
}
结果
具体策略A的策略方法被访问!
-----------------
具体策略B的策略方法被访问!
开发中常见的场景
- JAVASE中GUI编程中,布局管理
- Spring框架中,Resource接口,资源访问策略
- javax.servlet.http.HttpServlet#service()
实例分析
实例一——客户报价策略
某个市场人员接到单后的报价策略(CRM系统中常见问题)。报价策略很复杂,可以简单作如下分类:
• 普通客户小批量报价
• 普通客户大批量报价
• 老客户小批量报价
• 老客户大批量报价
具体选用哪个报价策略,这需要根据实际情况来确定。
传统方式实现
主要采用判断实现
public double getPrice ( String type, double price ) {
if (type.equals("普通客户小批量")) {
System.out.println("不打折,原价");
return price;
} else if (type.equals("普通客户大批量")) {
System.out.println("打九折");
return price * 0.9;
} else if (type.equals("老客户小批量")) {
System.out.println("打八五折");
return price * 0.85;
} else if (type.equals("老客户大批量")) {
System.out.println("打八折");
return price * 0.8;
}
return price;
}
分析
优点
实现起来比较容易,符合一般开发人员的思路
缺点
假如,类型特别多,算法比较复杂时,整个条件控制代码会变得很长,难于维护。
策略模式实现
UML类图
代码实现
抽象策略类
public interface Strategy {
//价格策略
double getPrice(double standardPrice);
}
具体策略类
新客户小批量价格策略
public class NewCustomerFewStrategy implements Strategy {
@Override
public double getPrice(double standardPrice) {
System.out.println("不打折,原价");
return standardPrice;
}
}
新客户大批量价格策略
public class NewCustomerManyStrategy implements Strategy {
@Override
public double getPrice(double standardPrice) {
System.out.println("打九折");
return standardPrice*0.9;
}
}
老客户小批量价格策略
public class OldCustomerFewStrategy implements Strategy {
@Override
public double getPrice(double standardPrice) {
System.out.println("打八五折");
return standardPrice*0.85;
}
}
老客户大批量价格策略
public class OldCustomerManyStrategy implements Strategy {
@Override
public double getPrice(double standardPrice) {
System.out.println("打八折");
return standardPrice*0.8;
}
}
环境类
- 负责和具体的策略类交互
- 这样的话,具体的算法和直接的客户端调用分离了,使得算法可以独立于客户端独立的变化。
- 如果使用spring的依赖注入功能,还可以通过配置文件,动态的注入不同策略对象,动态的切换不同的算法.
public class Context {
//当前采用的算法对象,聚合策略对象
private Strategy strategy;
//可以通过构造器来注入
public Context(Strategy strategy) {
super();
this.strategy = strategy;
}
//可以通过set方法来注入
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
//提供给客户端调用,得到报价
public void pringPrice(double s){
System.out.println("您该报价:"+strategy.getPrice(s));
}
}
客户端调用
public class Client {
public static void main(String[] args) {
//老客户大批量策略
Strategy s1 = new OldCustomerManyStrategy();
//构建环境
Context ctx = new Context(s1);
//得到报价
ctx.pringPrice(998);
}
}
结果
打八折
您该报价:798.4000000000001