3

策略模式详解

 3 years ago
source link: https://mp.weixin.qq.com/s/xpOKSUeRudqm6p3E8aBqlA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

yEFvUrZ.png!mobile

策略模式(Strategy Pattern)定义了一组同类型的算法,在不同的类中封装起来,每种算法可以根据当前场景相互替换,从而使算法的变化独立于使用它们的客户端(即算法的调用者)。《 GoF 设计模式》书中,它是这样定义的:

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

例如:在网购中,我在支付的时候,可以根据实际情况来选择不同的支付方式(微信支付、支付宝、银行卡支付等等),这些支付方式即是不同的策略。我们通常会看到如下的实现代码:

Order order = 订单信息
if (payType == 微信支付) {
    微信支付流程
} else if (payType == 支付宝) {
    支付宝支付流程
} else if (payType == 银行卡) {
    银行卡支付流程
} else {
    暂不支持的支付方式
}

如上代码,虽然写起来简单,但违反了面向对象的 2 个基本原则:

  • 单一职责原则 :一个类只有1个发生变化的原因

    之后修改任何逻辑,当前方法都会被修改

  • 开闭原则 :对扩展开放,对修改关闭

    当我们需要增加、减少某种支付方式(积分支付/组合支付),或者增加优惠券等功能时,不可避免的要修改该段代码

特别是当 if-else 块中的代码量比较大时,后续的扩展和维护会变得非常复杂且容易出错。在阿里《Java开发手册》中,有这样的规则: 超过3层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现

策略模式是解决过多 if-else (或者  switch-case ) 代码块的方法之一,提高代码的可维护性、可扩展性和可读性。下面我将从策略的定义、创建和使用这三个方面以上述网购支付为示例来分别进行说明。

1. 策略的定义

策略接口的定义 ,通常包含两个方法:获取策略类型的方法和处理策略业务逻辑的方法。

/**
 * 第三方支付
 */
public interface Payment {

    /**
     * 获取支付方式
     * 
     * @return 响应,支付方式
     */
    PayTypeEnum getPayType();

    /**
     * 支付调用
     * 
     * @param order 订单信息
     * @return 响应,支付结果
     */
    PayResult pay(Order order);

}

策略接口的实现 ,每种支付类都实现了上述接口(基于接口而非实现编程),这样我们可以灵活的替换不同的支付方式。下边示例代码展示了每种支付方式的实现:

/**
 * 微信支付
 */
@Component
public class WxPayment implements Payment {

    @Override
    public PayTypeEnum getPayType() {
        return PayTypeEnum.WX;
    }

    @Override
    public PayResult pay(Order order) {
        调用微信支付
        if (成功) {
            return PayResult.SUCCESS;
        } else {
            return PayResult.FAIL;
        }
    }

}
/**
 * 支付宝支付
 */
@Component
public class AlipayPayment implements Payment {

    @Override
    public PayTypeEnum getPayType() {
        return PayTypeEnum.ALIPAY;
    }

    @Override
    public PayResult pay(Order order) {
        调用支付宝支付
        if (成功) {
            return PayResult.SUCCESS;
        } else {
            return PayResult.FAIL;
        }
    }

}
/**
 * 银行卡支付
 */
@Component
public class BankCardPayment implements Payment {

    @Override
    public PayTypeEnum getPayType() {
        return PayTypeEnum.BANK_CARD;
    }

    @Override
    public PayResult pay(Order order) {
        调用银行卡支付
        if (成功) {
            return PayResult.SUCCESS;
        } else {
            return PayResult.FAIL;
        }
    }

}

2. 策略的创建

策略模式包含一组同类的策略,在使用时我们通常通过类型来判断创建哪种策略来进行使用。我们可以使用工厂模式来创建策略,以屏蔽策略的创建细节。如下代码所示:

public class PaymentFactory {
    private static final Map<PayTypeEnum, Payment> payStrategies = new HashMap<>();

    static {
        payStrategies.put(PayTypeEnum.WX, new WxPayment());
        payStrategies.put(PayTypeEnum.ALIPAY, new AlipayPayment());
        payStrategies.put(PayTypeEnum.BANK_CARD, new BankCardPayment());
    }

    public static Payment getPayment(PayTypeEnum payType) {
        if (payType == null) {
            throw new IllegalArgumentException("pay type is empty.");
        }
        if (!payStrategies.containsKey(payType)) {
            throw new IllegalArgumentException("pay type not supported.");
        }
        return payStrategies.get(payType);
    }

}

或者使用 Spring 创建:

@Component
public class PaymentFactory implements InitializingBean, ApplicationContextAware {
    private static final Map<PayTypeEnum, Payment> payStrategies = new HashMap<>();

    private ApplicationContext appContext;

    public static Payment getPayment(PayTypeEnum payType) {
        if (payType == null) {
            throw new IllegalArgumentException("pay type is empty.");
        }
        if (!payStrategies.containsKey(payType)) {
            throw new IllegalArgumentException("pay type not supported.");
        }
        return payStrategies.get(payType);
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
        appContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
        // 将 Spring 容器中所有的 Payment 接口实现类注册到 payStrategies
        appContext.getBeansOfType(Payment.class)
                  .values()
                  .forEach(payment -> payStrategies.put(payment.getPayType(), payment));
    }
}

注意:以上两种创建方式,都是无状态的,即不包含成员变量,它们可以被共享使用。如果策略类是有状态的,需要根据业务场景每次创建新的策略对象,那么我们可以在工厂方法中,每次生成新的策略对象,而不是使用已经提前缓存好的策略对象。如下代码所示:

public class PaymentFactory {
    public static Payment getPayment(PayTypeEnum payType) {
        if (payType == null) {
            throw new IllegalArgumentException("pay type is empty.");
        }
        if (payType == PayTypeEnum.WX) {
            return new WxPayment();
        }
        if (payType == PayTypeEnum.ALIPAY) {
            return new AlipayPayment();
        }
        if (payType == PayTypeEnum.BANK_CARD) {
            return new BankCardPayment();
        }
        throw new IllegalArgumentException("pay type not supported.");
    }

}

3. 策略的使用

通常我们事先并不知道会使用哪个策略,在程序运行时根据配置、用户输入、计算结果等来决定到底使用哪种策略。例如,前边支付方式的例子,我们会根据用户的选择来决定使用哪种支付方式。使用策略模式的代码实现如下:

Order order = 订单信息
PayResult payResult = PaymentFactory.getPayment(payType).pay(order);
if (payResult == PayResult.SUCCESS) {
    System.out.println("支付成功");
} else if (payType == 支付宝) {
    System.out.println("支付失败");
}

综上代码中,接口类只负责业务策略的定义,每个策略的具体实现单独放在实现类中,工厂类 Factory 只负责获取具体实现类,而具体调用代码则负责业务逻辑的编排。这些实现用到了面向接口而非实现编程,满足了职责单一、开闭原则,从而达到了功能上的高内聚低耦合、提高了可维护性、扩展性以及代码的可读性。

另外,可关注我的公众号或者QQ群(781374180),2021年我将系统化的分享 Java 技术栈相关内容,包括但不限于:Java 基础、MyBatis、MySQL、Spring 全家桶、MQ、Redis、ES、云原生、ServiceMesh、Serverless等。

6vMNNb.png!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK