本文是软件构造系列的第七篇文章。其余文章在:
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 继承,去实现个性操作。单一责任使得这些继承只需要关注算法的共性,而无需关注执行算法的环境,这给开发者和用户带来了很大便利。