简简单单设计模式-策略

0 阅读6分钟

策略模式是一种行为型模式,核心是将不同的算法/业务逻辑封装成为独立的策略类,使它们可以相互替换,且策略的变化不影响使用策略的客户端。

代码实现

比如说在支付业务中,需要根据不同的支付方式处理支付结果。如以下代码所示:

public class PaymentService {
​
    /**
     * 统一支付方法:通过if-else分支区分支付方式
     */
    public String pay(String payType, BigDecimal amount) {
        // 校验金额(通用逻辑)
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            return "支付金额必须大于0!";
        }
​
        // 核心分支:不同支付方式的逻辑耦合在这里
        if ("alipay".equals(payType)) {
            // 支付宝支付逻辑(硬编码)
            String alipayResult = "调用支付宝接口,订单号:" + generateOrderNo()
                    + ",支付金额:" + amount + "元 → 支付成功";
            // 支付宝特有逻辑:如签名验证、回调处理等
            verifyAlipaySign();
            return alipayResult;
        } else if ("wxpay".equals(payType)) {
            // 微信支付逻辑(硬编码)
            String wxpayResult = "调用微信支付接口,订单号:" + generateOrderNo()
                    + ",支付金额:" + amount + "元 → 支付成功";
            // 微信特有逻辑:如appid校验、签名生成等
            generateWxPaySign();
            return wxpayResult;
        } else if ("unionpay".equals(payType)) {
            // 银联支付逻辑(硬编码)
            String unionpayResult = "调用银联支付接口,订单号:" + generateOrderNo()
                    + ",支付金额:" + amount + "元 → 支付成功";
            // 银联特有逻辑:如证书校验等
            verifyUnionPayCert();
            return unionpayResult;
        } else {
            return "不支持的支付方式:" + payType;
        }
    }
​
​
    // 通用工具方法:生成订单号
    private String generateOrderNo() {
        return "ORDER_" + System.currentTimeMillis();
    }
​
    // 支付宝特有方法
    private void verifyAlipaySign() {
        System.out.println("校验支付宝签名...");
    }
​
    // 微信支付特有方法
    private void generateWxPaySign() {
        System.out.println("生成微信支付签名...");
    }
​
    // 银联特有方法
    private void verifyUnionPayCert() {
        System.out.println("校验银联证书...");
    }
}
​
class PaymentServiceTest {
    public static void main(String[] args) {
        PaymentService paymentService = new PaymentService();
        // 支付宝支付
        System.out.println(paymentService.pay("alipay", new BigDecimal("100")));
        // 微信支付
        System.out.println(paymentService.pay("wxpay", new BigDecimal("200")));
    }
}
​

如果以后加入新的处理方式,这个支付实现类需要改变,且不利于扩展。如果出现组合支付,这个逻辑代码还要改动。根据设计原则:

找出应用中可能需要变化之处,把它们独立出来,不要和那些需要变化的代码混在一起

把变化的部分取出并“封装”起来,好让其他部分不受影响。代码变化引起的不经意后果变少,系统会变得更有弹性。所以将if代码块里面的逻辑抽出来,形成不同的支付方式,这就是把将来可能变化的代码独立出来。

独立出来的支付方法怎么设计呢,支付可以认为是一种行为方式,不同的支付方式则代表具体的行为。所以可以用接口代表每个行为,实现接口就是表示具体的行为。这里用到了一个设计原则:

针对接口编程,而不是针对实现编程

然后在支付实现类添加一个容器,配置具体支付策略对象,定义支付方法,支付实现类通过容器获取支付策略,如下面代码所示:

public class PaymentService {
​
    private static final Map<String, PaymentStrategy> paymentStrategies = new HashMap<>();
​
    static {
        paymentStrategies.put("alipay", new AlipayStrategy());
        paymentStrategies.put("wxpay", new WechatPayStrategy());
        paymentStrategies.put("unionpay", new UnionPayStrategy());
    }
​
    /**
     * 统一支付方法:通过if-else分支区分支付方式
     */
    public String pay(String payType, BigDecimal amount) {
        // 校验金额(通用逻辑)
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            return "支付金额必须大于0!";
        }
        PaymentStrategy paymentStrategy = paymentStrategies.get(payType);
        // 核心分支:不同支付方式的逻辑耦合在这里
        if (paymentStrategy != null) {
            // 支付宝支付逻辑(硬编码)
            return paymentStrategy.pay(amount);
        } else {
            return "不支持的支付方式:" + payType;
        }
    }
}
​
class PaymentServiceTest {
    public static void main(String[] args) {
        PaymentService paymentService = new PaymentService();
        // 支付宝支付
        System.out.println(paymentService.pay("alipay", new BigDecimal("100")));
        // 微信支付
        System.out.println(paymentService.pay("wxpay", new BigDecimal("200")));
        // 银联支付
        System.out.println(paymentService.pay("unionpay", new BigDecimal("300")));
        // 不支持的支付方式
        System.out.println(paymentService.pay("applepay", new BigDecimal("400")));
        // 金额校验失败
        System.out.println(paymentService.pay("alipay", new BigDecimal("0")));
    }
}
​
​
interface PaymentStrategy {
​
    String pay(BigDecimal amount);
​
    String generateOrderNo();
​
    void verifySign();
}
​
class WechatPayStrategy implements PaymentStrategy {
    @Override
    public String pay(BigDecimal amount) {
        String wxpayResult = "调用微信支付接口,订单号:" + generateOrderNo()
                + ",支付金额:" + amount + "元 → 支付成功";
        verifySign();
        return wxpayResult;
    }
​
    @Override
    public String generateOrderNo() {
        return "ORDER_" + System.currentTimeMillis();
    }
​
    @Override
    public void verifySign() {
        System.out.println("生成微信支付签名...");
    }
}
​
class AlipayStrategy implements PaymentStrategy {
    @Override
    public String pay(BigDecimal amount) {
        String alipayResult = "调用支付宝接口,订单号:" + generateOrderNo()
                + ",支付金额:" + amount + "元 → 支付成功";
        verifySign();
        return alipayResult;
    }
​
    @Override
    public String generateOrderNo() {
        return "ORDER_" + System.currentTimeMillis();
    }
​
    @Override
    public void verifySign() {
        System.out.println("校验支付宝签名...");
    }
}
​
class UnionPayStrategy implements PaymentStrategy {
    @Override
    public String pay(BigDecimal amount) {
        // 银联支付逻辑(硬编码)
        String unionpayResult = "调用银联支付接口,订单号:" + generateOrderNo()
                + ",支付金额:" + amount + "元 → 支付成功";
        verifySign();
        return unionpayResult;
    }
​
    @Override
    public String generateOrderNo() {
        return "ORDER_" + System.currentTimeMillis();
    }
​
    @Override
    public void verifySign() {
        System.out.println("校验银联证书...");
    }
}

这样在我们新增支付方式时,就不需要改动支付实现类的代码了,只需要添加一个新的支付策略类实现接口方法。

将未来需要改变的和不改变的代码分离,并用实现而非继承的方式,这符合OO设计思想。后续可以添加策略工厂,降低策略对象的耦合。

核心结构(UML类图)

通过以上的例子对策略模式有了了解,现在再看策略模式的UML类图就容易理解了,如下:

策略模式.png

  • Strategy(策略,如PaymentStrategy)

    • 定义所有支持的算法的公共接口,如支付算法。Context使用这个接口来调用某个ConcreteStrategy 定义的算法。
  • ConcreteStrategy(具体策略,如WechatPayStrategy,AlipayStrategy)

    • 以Strategy接口实现某个具体算法,如微信的支付算法,支付宝的支付算法。
  • Context(上下文,如PaymentService)

    • 用一个ConcreteStrategy 对象来配置,如定义一个HashMap来具体算法对象。
    • 定义一个接口来让Strategy访问它的数据。
    • 维护一个对Strategy 对象的引用。

Strategy和Context 可以相互组合实现选定的算法,就像文中实现的支付策略一样。

框架中的应用

MyBatis的Interceptor接口是框架最重要的扩展点之一,框架实现了不同的拦截器,也可以自定义实现Interceptor,这就是策略模式的体现:

public interface Interceptor {
    Object intercept(Invocation var1) throws Throwable;
​
    Object plugin(Object var1);
​
    void setProperties(Properties var1);
}
​
// 具体策略实现
@Component
@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {...})
})
public class PerformanceMonitorInterceptor implements Interceptor {
    // 性能监控策略
}
​
@Component  
@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {...})
})
public class DataPermissionInterceptor implements Interceptor {
    // 数据权限策略
}
​
@Component
@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {...})
})
public class PaginationInterceptor implements Interceptor {
    // 分页策略
}
总结

如果说只有几种策略,并且未来是不会被改变的,那么可以选择不用策略模式。否则建议还是使用策略模式,它的优势在于:客户端可以动态的切换策略而无需改变代码、新增策略也只添加新的策略类不会改变原来代码、替换 if-elseswitch 语句、策略类逻辑清晰职责单一。