设计模式之策略模式
策略模式定义了算法簇,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
来解析这个定义,“算法簇”&“相互替换”代表了是一组相同目的算法,肯定是利用多态能力实现。脑海中,下述的类图就出来了。

“变化独立于使用算法的客户”,这句话的意思是,使用这些算法的客户端代码不需要因为算法的变化而修改自身,什么意思,如何能做到改变算法而不修改自身,那就是客户端代码只依赖于算法的抽象(接口),而不是具体的实现。
通过类图,可以清晰的看到两者的区别。


学习设计模式,一个关键的自我校验,是能够自己给自己出题,构建场景,并通过目标设计模式来解决。
- 定义算法接口:定义一个
PaymentStrategy
接口,表示支付方式的抽象。
public interface PaymentStrategy {
void pay(int amount);
}
- 实现具体算法:
AlipayStrategy
和WechatPayStrategy
分别实现PaymentStrategy
接口。
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("使用支付宝支付:" + amount + "元");
}
}
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("使用微信支付:" + amount + "元");
}
}
- 动态替换算法:客户端可以根据用户的选择,使用支付宝或微信支付,通过持有算法接口的引用,可以动态切换具体的算法实现。
public class Client {
public static void main(String[] args) {
// 使用支付宝支付
PaymentStrategy strategy = new AlipayStrategy();
strategy.pay(100);
// 使用微信支付
PaymentStrategy strategy = new WechatPayStrategy();
strategy.pay(300);
}
}
至此,一个简单的策略模式模式实现就完成了。

但是,这个实现并不完美,让我们再回顾一遍策略模式的定义,策略模式定义了算法簇,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。现有的实现能够让算法的变化独立于使用算法的客户吗?
一方面,如果支付的方法发生了变化,如pay()变成了pay(int mount),那么client的代码就要发生变化,实际应用中,可不止client一个,还有client-1、client-2....client-10,所有的client方调用代码都要进行修改,明显不符合“算法的变化独立于使用算法的客户”这个要求。
另一方面,client除了关联抽象类,还关联了具体类,没有完全解耦。
最后,client除了要负责策略的创建,还要负责策略的调用,职责过多,可以考虑将一部分职责抽离出去。
一个简单的实现,就发现了三个方面的不完美,能够发现并理解这些问题,才知道如何改进,才知道代码为何要如此设计,理解是最重要的一步。再次强调,设计模式不能靠死记硬背,而要基于理解,通过基石加上原则来反复考量现有设计。
开始改良,很明显,需要抽离出一个类,来讲client与具体策略解耦,并负责策略的创建,当pay()发生改动时,只需要这一个类即可。
新增定义上下文类:提供一个方法供客户端调用。
// 上下文类
public class PaymentContext {
private PaymentStrategy strategy;
// 设置具体策略
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
// 执行支付
public void executePayment(int amount) {
if (strategy != null) {
strategy.pay(amount);
} else {
System.out.println("未设置支付策略!");
}
}
}
重写客户端代码。
// 客户端代码
public class Client {
public static void main(String[] args) {
// 创建上下文对象
PaymentContext context = new PaymentContext();
// 使用支付宝支付
context.setStrategy(new AlipayStrategy());
context.executePayment(100); // 输出:使用支付宝支付:100元
// 使用微信支付
context.setStrategy(new WechatPayStrategy());
context.executePayment(200); // 输出:使用微信支付:200元
// 动态切换策略
context.setStrategy(new AlipayStrategy());
context.executePayment(300); // 输出:使用支付宝支付:300元
}
}
针对这个实现,再来考核之前三个方面的问题。
- 如果pay()变成了pay(int mount),只需要修改PaymentContext的代码,客户端client-1、client-2...client-100都不用改变。符合要求。
- client关联了具体类,没有完全解耦。这个问题似乎还存在,client依然关联到了具体类,但是注意,这与前一版的实现,有很大的区别,前一版中客户端代码除了知道具体类的存在,还调用到了具体的方法,而改良后,客户端代码只知道具体类的存在而已,耦合深度大大降低,在实际实现中,可通过配置文件实现解耦。如果要完全解除对具体类的关联,可通过工厂模式或依赖注入的方式实现。
- 改良后客户端代码只负责设置策略和触发支付,具体的支付逻辑由 PaymentContext和策略类负责。
并且,后续增加策略时,客户端代码只需要设置新的策略对象,而不需要修改调用逻辑。
