在看本文之前,强烈建议大家先看看这篇文章:Java策略模式 ,其中的例子十分生动,也十分清晰,不过在评论中对是策略模式还是状态模式产生了争议。下面完整的说一说策略模式,其实策略模式最重要的前提是:策略模式的客户端环境(Context)必须知道所有的策略类、理解这些不同策略类算法之间的区别,并自行决定使用哪一个策略类来完成业务逻辑。
策略模式:针对一组算法或行为特性,将它们抽象到具有共同接口函数的独立抽象类或接口中,从而使得它们可以互相转换。这样就使得某一个特定的接口行为可以在不影响客户端的情况下发生变化。
表面看来,策略模式不能严格称为一种设计模式,因为它仅仅是一个接口的多个实现在运行期的选择性替换而已。因而,要关注的策略模式的重点并不在于这些不同实现类的具体算法的实现机制,而在于当我们面对一个业务场景可能具备有多种行为,且这多种行为之间可以进行替换时,如何将它们行为特性的公共部分进行抽象,并最终形成一个统一的接口的过程。
环境(Context)角色——持有一个Strategy类的引用,将决定调用哪种Strategy角色完成业务逻辑。
抽象策略(Strategy)角色——抽象的策略角色,由一个接口或抽象类来扮演这个角色。是策略模式的核心,它是所有策略算法的核心归纳。因而对外表现为一个一致的接口函数。
具体策略(ConcreteStrategy)角色——封装了具体的策略算法或行为。
策略模式的核心就是对算法的包装,最终目的是把使用算法的责任(环境)和算法的实现进行解耦,由于环境和算法的独立,算法的增加、减少或者修改都不会影响到环境和客户端的调用,当出现新的算法或者现有算法的实现发生变化时,我们只需要改变具体的策略类,并在客户端调用的地方进行注册即可。在这种情况下,策略模式中的算法实现都是“可插拔(Pluggable)”的。
一个具体例子,在电商打折金额减免时的策略:
一个抽象策略,对价格计算进行抽象:
public interface PriceStrategy {
//计算总价格的方法
public double calculatePrice();
}
策略角色实现类:不同的“价格计算方法”,下面两种
无折扣的方法:
public class NoDiscountStrategy implements PriceStrategy{
private double price;
public NoDiscountStrategy(double price){
this.price = price;
}
//没有任何减免和折扣的计算
@Override
public double calculatePrice() {
System.out.println("无折扣,总价格:"+price);
return this.price;
}
}
打折的方法:
public class DiscountStrategy implements PriceStrategy{
private double price;
private double rate;
public DiscountStrategy(double price, double rate){
this.price = price;
this.rate = rate;
}
@Override
public double calculatePrice() {
System.out.println("满减打折,总价格:"+price*rate);
return price*rate;
}
}
客户端环境:main函数
public class Test {
public static void main(String[] args) {
//价格
double price = 700;
PriceStrategy strategy = null;
if(price<300){
strategy = new NoDiscountStrategy(price);
}else if(price>=300){
strategy = new DiscountStrategy(price, 0.9);
}
strategy.calculatePrice();
}
}
策略模式的核心在于抽象,对所有核心算法的行为接口的抽象统一,例子中的calculatePrice()就是对“价格计算”这一行为的统一化抽象。
在完成了抽象之后,策略模式在调用时需要完成另外一个核心要义:选择。选择,指的是在运行期不同的算法实现之间进行选择,这个选择权掌握在客户端调用手中,具体的策略类无从知晓当前的策略类到底适用于哪一种实际业务场景的。
以上就是策略模式的具体实现。
下边说那篇文章中刘备东吴娶亲的例子,评论中的争论,主要是在于“选择”的问题。其实那个例子是很好的策略模式的例子,只不过是逻辑上没有体现出选择算法的过程罢了,不用太过纠结这个问题,不过关于“选择”还是可以深究的:
策略模式使用过程中的重要前提条件是:客户端环境(Context)必须知道所有的策略类,理解这些不同策略算法之间的区别,并自行决定使用哪一个策略类来完成业务逻辑。
在这种情况下,“做决定”将会成为客户端环境颇为头疼的问题,因为在做决定的时候,客户端环境将不得不借助某些复杂的多重条件判断来完成。比如在价格的例子中,使用if/else的多重条件判断,在实际中可能还会有更加复杂的选择判断。
可以利用策略模式中“客户端环境必须知晓所有的策略类”这一特性,将多重条件判断进行一次再抽象,从而完成策略决定权的下放。
把例子全部重构:
PriceStrategy.java:
public interface PriceStrategy {
//判断金额是否满足减免策略
public boolean match(double price);
//计算总价格的方法
public double calculatePrice();
}
NoDiscountStrategy.java
public class NoDiscountStrategy implements PriceStrategy{
private double price;
public NoDiscountStrategy(double price){
this.price = price;
}
//没有任何减免和折扣的计算
@Override
public double calculatePrice() {
System.out.println("无折扣,总价格:"+price);
return this.price;
}
@Override
public boolean match(double price) {
return price<300;
}
}
DiscountStrategy.java
public class DiscountStrategy implements PriceStrategy{
private double price;
private double rate;
public DiscountStrategy(double price, double rate){
this.price = price;
this.rate = rate;
}
@Override
public double calculatePrice() {
System.out.println("满减打折,总价格:"+price*rate);
return price*rate;
}
@Override
public boolean match(double price) {
return price>=300;
}
}
Main.java
public class Test {
public static void main(String[] args) {
//这里是得到的金额
double price = 700;
// List<PriceStrategy> strategies = getPriceStrategies();
//客户端必须知晓所有的策略类
List<PriceStrategy> strategies = new ArrayList<PriceStrategy>();
strategies.add(new NoDiscountStrategy(price));
strategies.add(new DiscountStrategy(price, 0.9));
//选择权的转移
for(PriceStrategy strategy: strategies){
if(strategy.match(price)){
strategy.calculatePrice();
}
}
}
}
这样一来,客户端在策略的选择上,就不会陷入if/else的困境,因为策略类作为一个算法类,它自身的调用特性(满足什么条件下,策略类的使用条件成立并可以被调用)也被封装在策略类的内部。从代码的特性看,这样做能够把多重条件判断转化为一个循环+判断的结构,从而极大的提高了代码的可读性和可维护性。这在一定程度上颠覆了策略模式的设计结构,因为客户端在决定调用策略类的时候,并没有自己来决定,而是去询问具体的策略类。然而,这种策略类的转移,却也能够在某些特定的业务场景中显示出其强大的威力。