策略模式用于封装系列的算法,这些算法通常被封装在一个被称为Context的类中,客户端程序可以自由选择其中一种算法,或让Context为客户端选择一个最佳算法——使用策略模式的又是是为了支持算法的自由切换。
考虑如下场景:假设我们正在开发一个网上书店:该书店为了促销,经常需要对图书进行打折,程序需要考虑各种打折促销的计算方法。为了实现书店现在所提供的各种打折需求,程序考虑使用如下方式来实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
double
discount(
double
origin) {
switch
(getDiscountType()) {
case
VIP_DISCOUNT:
return
vipDiscount(origin);
break
;
case
OLD_DISCOUNT:
return
oldDiscount(origin);
break
;
case
NOM_DISCOUNT:
return
nomDiscount(origin);
break
;
default
:
break
;
}
}
|
上面的代码中根据打折类型来决定使用不同的打折算法,从而满足该书店促销打折的需求。从功能实现的角度来看,这段代码没有太大的问题。但这段程序中各种打折方法都被直接写入了discount方法中,如果有一天,该书店需要新增一种打折类型,那么开发人员必须至少修改3处代码:首先需要增加一个常量,该常量代表新增的打这类型;其次需要在switch语句中增加一个case语句;最后开发人员需要实现xxxDiscount方法,用于实现新的打着算法。
为了改变这种不好的设计,下面会选择使用策略模式来实现该功能。下面先提供一个打折算法的接口,该接口里包含了一个getDiscount()方法:
1
2
3
|
public
interface
DiscountStrategy {
double
getDiscount(
double
originPrice);
}
|
下面为该打折接口提供两个策略类,它们分别实现了不同的打折算法:
1
2
3
4
5
6
7
|
public
class
VipDiscount
implements
DiscountStrategy {
@Override
public
double
getDiscount(
double
originPrice) {
System.out.println(
"使用VIP打折"
);
return
originPrice *
0.5
;
}
}
|
OldDiscount.java
1
2
3
4
5
6
7
|
public
class
OldDiscount
implements
DiscountStrategy {
@Override
public
double
getDiscount(
double
originPrice) {
System.out.println(
"使用旧书折扣"
);
return
originPrice *
0.7
;
}
}
|
提供了上面两个策略类后,程序还应该提供一个DiscountContext类,该类用于为客户端代码选择合适的折扣策略,当然也允许用户自由选择折扣策略:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class
DiscountContext {
private
DiscouontStrategy strategy;
public
DiscountContext(DiscountStrategy strategy) {
this
.strategy = strategy;
}
public
double
getDiscount(
double
price) {
if
(strategy ==
null
) {
strategy =
new
OldDiscount();
}
return
strategy.getDiscount(price);
}
//切换策略算法的方法:
public
void
changeDiscount(DiscountStrategy strategy) {
this
.strategy = strategy;
}
}
|
从上面的代码可以看出,该Context类扮演了决策者的角色,它决定调用哪个折扣策略来处理图书打折。当客户端代码没有选择合适的折扣时,该Context会自动选择OldDiscount策略,用户也可以根据需要自定义策略(当然实际情况肯定不止这一种)。下面是一个测试程序:
1
2
3
4
5
6
7
8
9
|
public
class
StrategyTest {
public
static
void
main(String args[]) {
//开始时不自定义打折策略
DiscountContext context =
new
DiscountStrategy(
null
);
System.out.println(
"100元的书默认打折后是:"
+ context.getDiscount(
100
));
context.changeDiscount(
new
VipDiscount());
System.out.println(
"100元的书VIP打折后是:"
+ context.getDiscount(
100
));
}
}
|
创建DiscountContext对象时,并没有指定打折策略,因此程序将使用默认的打折策略,然后用户创建了一个VIP打折策略,并改变了DiscountContext对象中的打折策略,于是可以看到使用VIP打折后的结果。
现在再次考虑刚才的情形:当业务需要增加一种打折类型时,系统只需要新定义一个DiscountStrategy实现类,该实现类实现getDiscount方法,用于实现新的打折算法即可。客户端需要切换新的打折策略时,则需要先调用DiscountContext的changeDiscount方法切换即可。
从上面的介绍可以看出,使用策略模式可以让客户端代码在不同的打折策略之间切换,但也有不足:客户端代码需要和不同的策略类耦合。为了弥补这个不足,我们可以考虑使用配置文件来指定DiscountContext使用哪一种策略,这样就彻底分离了客户端代码和具体打折策略类。