本文是软件构造系列的第七篇文章。其余文章在:
https://ruanx.net/tag/software-construction/
求最小生成树有 Kruskal 和 Prim 两种算法;从长沙去哈尔滨可以乘坐火车或者飞机;在 Steam 购买游戏可以选择支付宝、微信或 visa 信用卡支付。这些都是同一个任务的多种实现方式。以 Steam 为例,支付的逻辑可能写成下面的形式:
class BuyGame {
void pay(String via) {
if ("Alipay".equals(via)) {
System.out.println("Pay with Alipay");
}
if ("Wechat".equals(via)) {
System.out.println("Pay with Wechat");
}
if ("Visa".equals(via)) {
System.out.println("Pay with visa card");
}
}
}
客户端通过参数传入支付方式:
public static void main(String[] args) {
BuyGame order = new BuyGame();
order.pay("Alipay");
}
看起来是解决了问题,但考虑一个情形:现在除了上述几种支付方式外,要添加 master 信用卡、银联、Paypal 支付。我们将不得不修改 BuyGame
类的代码,这违背了开闭原则。
策略模式可以解决这个问题。它的思想是基于单一责任:把算法实现与算法使用分离开,用若干个独立的类(策略类)专门实现算法。算法的调用者(称为“环境类”)在执行任务时,委派一个策略类来完成任务。
按照策略模式,首先需要把支付方式分拆到自己的类里面:
interface PaymentStrategy {
void pay();
}
class AlipayStrategy implements PaymentStrategy {
public void pay() {
System.out.println("Pay with Alipay");
}
}
class WechatStrategy implements PaymentStrategy {
public void pay() {
System.out.println("Pay with Wechat");
}
}
class VisaStrategy implements PaymentStrategy {
public void pay() {
System.out.println("Pay with visa card");
}
}
BuyGame
委派一个支付策略来实现支付逻辑:
class BuyGame {
private PaymentStrategy via;
public BuyGame(PaymentStrategy via) {
this.via = via;
}
public void pay() {
via.pay();
}
}
现在,客户想通过 Alipay 来支付一笔订单,只需要把一个 AlipayStrategy
实例交给 BuyGame,再调用 pay()
接口:
public static void main(String[] args) {
BuyGame order = new BuyGame(new AlipayStrategy());
order.pay();
}
很明显,想要添加新的支付方式,只需要添加几个对 PaymentStrategy
的实现类即可。无需改动其他部分的代码;client 代码也无需改变,完美地实现了开闭原则。
另外,策略模式还有一个好处:既然算法实现是分拆成了多个类来实现,它们就可以通过继承来复用代码。例如,visa 和 master 支付都有 “输入信用卡号” 等步骤,可以用一个父类实现这些共性操作,再由 visa 和 master 继承,去实现个性操作。单一责任使得这些继承只需要关注算法的共性,而无需关注执行算法的环境,这给开发者和用户带来了很大便利。