策略模式详解

图片

策略模式(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个发生变化的缘由缓存

    以后修改任何逻辑,当前方法都会被修改微信

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

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

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

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

1. 策略的定义

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

/**
* 第三方支付
*/

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 只负责获取具体实现类,而具体调用代码则负责业务逻辑的编排。这些实现用到了面向接口而非实现编程,知足了职责单1、开闭原则,从而达到了功能上的高内聚低耦合、提升了可维护性、扩展性以及代码的可读性。

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

相关文章
相关标签/搜索