[springboot advanced] How to elegantly use the strategy pattern to solve the problem of multi-payment scenarios

Table of contents

1. Introduction to Strategy Pattern

Second, the usage scenarios of the strategy pattern

3. Application of Strategy Pattern

1. Input and output parameters

2. Policy interface

3. Implementation of the strategy

4. Strategy testing

3. Some usage skills

Four. Summary


For a functional application with relatively complex logic, it is inevitable to make a lot of logical judgments and write a bunch of if/else. What's worse is that there will be a lot of if/else nested in it. If the code is not commented, then It just drives people crazy. At this time, you may consider using the strategy pattern to process some codes with the same logic, so as to avoid the long string of if/else in the code body 

1. Introduction to Strategy Pattern

You can look at a class diagram first, and it doesn’t matter if you don’t understand it. The author was also confused when I first got in touch. The third section will introduce how to apply it to actual problems in human terms, so students can have a first. concept.

This pattern involves three roles:

  ●  Environmental (Context) role: holds a Strategy reference.

  ●  Abstract strategy (Strategy) role: This is an abstract role, usually implemented by an interface or abstract class. This role gives all interfaces required by the concrete strategy classes.

  ●  ConcreteStrategy role: wraps related algorithms or behaviors.

Second, the usage scenarios of the strategy pattern

For example, the most common scenario is the payment method, which generally includes Alipay, WeChat, bank card, etc. If you look at it from the code, it looks like this.

//支付接口
pay(payType, orderNo) {

    if (payType == 微信支付) {
        微信支付处理
    } else if (payType == 支付宝) {
        支付宝支付处理
    } else if (payType == 银行卡) {
        银行卡支付处理
    } else {
        暂不支持的支付方式
    }

}

If we need to add a new payment method in the future, we need to change the code here and add an else if code. But doing so actually violates two basic principles of object-oriented:

        ● Single Responsibility Principle: A class should have only one reason for changes

        ● Open-closed principle: open for extension, closed for modification

3. Application of Strategy Pattern

Let's look at how the general strategy pattern is applied to the payment method scenario above.

1. Input and output parameters

Define the uniform input and output fields of the strategy interface, which is the same for all payment methods.

1) Order payment class

The entry class defines the fields that may be required for a payment order. Here are just a few of them. Pay attention to the channel field. This is the focus of our usage strategy, which will be introduced below.

/**
 * 订单支付
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Data
public class PayOrder {

    /**
     * 支付金额,单位元
     */
    private String mete;
    /**
     * 用户手机号
     */
    private String phone;
    /**
     * 支付渠道
     * ALIPAY:支付宝
     * WECHAT:微信
     * CARD:银行卡
     */
    private String channel;

}

2) Payment result class

The output class defines the fields that need to be returned after the order is paid. Here is a brief list of some.

/**
 * 支付结果
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Data
public class PayResult {

    /**
     * 订单号
     */
    private String order;

    /**
     * 支付结果
     * 1:成功
     */
    private Integer code;

}

2. Policy interface

Corresponding to the strategy class diagram mentioned in the first point, to define the interfaces involved in the strategy, but the author here will be somewhat different in actual use.

1) Unified entrance for payment processing

This is the payment interface exposed to external business calls.

/**
 * 支付处理服务统一入口
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
public interface VendorPaymentService {

    /**
     * 付款
     *
     * @param payOrder
     * @return
     */
    PayResult pay(PayOrder payOrder);

}

2) Payment processing service interface

Equivalent to the Strategy strategy role in the strategy class diagram.

/**
 * 支付处理服务
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
public interface PaymentHandleService {

    /**
     * 付款
     *
     * @param payOrder
     * @return
     */
    PayResult pay(PayOrder payOrder);

}

3) Payment processing context

To put it simply, it is to choose which specific strategy role to handle. You can see that the input parameter here is the channel field mentioned above, and the output parameter is the implementation of the specific strategy role interface after the strategy.

/**
 * 支付处理上下文
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
public interface PaymentContextService {

    /**
     * 获取处理上下文
     *
     * @param channel
     * @return
     */
    PaymentHandleService getContext(String channel);

}

3. Implementation of the strategy

Next, we will write their implementation classes for the interfaces defined above.

1) Unified entrance for payment processing services

The method needs to hold a context interface, which is used to obtain the specific payment processing class according to the channel of the order.

/**
 * 支付处理服务统一入口
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Service
public class VendorPaymentServiceImpl implements VendorPaymentService {

    @Resource
    PaymentContextService paymentContextService;

    @Override
    public PayResult pay(PayOrder payOrder) {
        //获取订单中的渠道
        String channel = payOrder.getChannel();
        //根据渠道,具体选择所使用的支付处理类
        PaymentHandleService handleService = paymentContextService.getContext(channel);
        //调用该支付处理类的支付方法
        return handleService.pay(payOrder);
    }

}

2) Payment processing context

This is the core part of the strategy. Find the corresponding specific strategy payment class according to the channel value of the order.

/**
 * 支付处理上下文
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Service
public class PaymentContextServiceImpl implements PaymentContextService {

    /**
     * 自动注入所有具体策略实现类
     */
    @Resource
    List<PaymentHandleService> handleServiceList;

    /**
     * 额外定义一个不支持的渠道支付方式实现类
     */
    @Resource(name = "NonsupportPaymentHandleServiceImpl")
    PaymentHandleService nonsupportService;

    @Override
    public PaymentHandleService getContext(String channel) {

        if (StrUtil.isEmpty(channel)) {
            return nonsupportService;
        }
        
        //策略实现类上都会打上 Payment 注解,并定义支付方式的值,用于适配订单的渠道值
        PaymentHandleService handleService = handleServiceList.stream()
                .filter(f -> StrUtil.equals(channel, f.getClass().getAnnotation(Payment.class).value()))
                .findFirst()
                .orElse(nonsupportService);

        return handleService;
    }

}

Payment Method Annotation Class

/**
 * 支付方式注解
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Payment {

    String value() default "";

}

Unsupported channel payment method implementation class

/**
 * 不支持的业务处理实现
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Payment("nonsupport")
@Service("NonsupportPaymentHandleServiceImpl")
public class NonsupportPaymentHandleServiceImpl implements PaymentHandleService {

    @Override
    public PayResult pay(PayOrder payOrder) {
        PayResult result = new PayResult();
        result.setCode(-1);
        return result;
    }
}

3) Payment method specific strategy class

Here is the implementation of Alipay payment. Other payments are similar. You can see that the annotation Payment defines the channel field of this payment as alipay

/**
 * 支付宝支付处理
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Slf4j
@Payment("alipay")
@Service
public class AlipayPaymentHandleServiceImpl implements PaymentHandleService {

    @Override
    public PayResult pay(PayOrder payOrder) {

        PayResult result = new PayResult();
        result.setOrder("alipay_202211261234567890");
        result.setCode(1);

        log.info("支付宝支付处理 订单信息:{} 支付结果:{}", payOrder, result);

        return result;
    }
}

4. Strategy testing

Let's write a unit test class, here we use Alipay to pay and see the effect.

@SpringBootTest
class SpringbootAdvanceDemoApplicationTests {

    @Resource
    VendorPaymentService vendorPaymentService;

    @Test
    void contextLoads() {

        PayOrder payOrder = new PayOrder();
        payOrder.setChannel("alipay");
        payOrder.setMete("100");
        payOrder.setPhone("123456");
        
        PayResult payResult = vendorPaymentService.pay(payOrder);
        System.out.println(payResult);
    }

}

As you can see in the context implementation class, our policy implementation class has been successfully injected here.

 According to the input channel value, the Alipay strategy implementation class is matched.

 Successfully jump to the Alipay strategy implementation class.

 Next, try to adjust the value of the channel to the wechat method to see the effect.

 Successfully jump to the WeChat strategy implementation class.

3. Some usage skills

In many cases, strategy patterns can also be nested. For example, the payment method exemplified above, if we can give discounts to users for certain situations in each payment, then we can write another set of strategies on the discount strategy. .

Four. Summary

Students may also feel that using the strategy pattern, we have to add several interfaces and classes. In fact, this is also one of its shortcomings. Every time a payment method (strategy) is added, a class needs to be added, so it The possibility of reuse is very small.

At the same time, it can also be seen from the above examples that the input and output parameters of the strategy are fixed, but in actual projects, they are often complex and changeable, so there are many more input and output parameters. Shared fields, this is also a problem for subsequent maintenance, because the caller is not necessarily familiar with which fields are required, so this may require relevant interface documents as an aid.

But for a large system, it may be more conducive to maintenance. After all, a payment method may itself be a complex function. If everyone writes on a piece of code, it will definitely be a disaster.

Guess you like

Origin blog.csdn.net/lrb0677/article/details/128051083