column

  • The practice of design pattern in e-commerce business — Strategic pattern
  • Practice of design pattern in e-commerce business — state pattern
  • Practice of design mode in e-commerce business — Responsibility chain mode
  • Practice of design pattern in e-commerce business — Template method pattern
  • .

Updated continuously.

background

We have mentioned the payment method of the order in the design mode practice under e-commerce business – state mode,

Now let’s dig a little deeper. Generally speaking, under the micro-service architecture, the order center and the payment center are separated. Here, the payment processing of the order center actually only updates the status of the order after monitoring the payment success notification message of the payment center. The specific business process of payment is completed in the payment center, and the payment page is uniformly closed by the payment center, commonly known as the cashier desk.

On the cashier page, we can generally choose different payment methods:

To start, we might write the code form for the payment interface as follows

The original code

Enumeration of payment methods

public enum PayTypeEnum {

    AliPay(1."Alipay Pay"),
    WeXinPay(2."Wechat Pay"),
    / /...
    ;

    private int code;
    private String desc;

    PayTypeEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode(a) {
        return this.code;
    }

    public String getDesc(a) {
        return this.desc;
    }

    public static PayTypeEnum getByCode(int code) {
        for (PayTypeEnum payTypeEnum : values()) {
            if (code == payTypeEnum.getCode()) {
                returnpayTypeEnum; }}throw new IllegalArgumentException("PayTypeEnum not exist, code="+ code); }}Copy the code

Pay for the service class

The payment method assumes four small steps, as shown in the code

public class PayService {

    /** * Payment method **@param payRequest
     * @return* /
    public PayResponse pay(PayRequest payRequest) {
        try {
            //1. Check basic parameters
            requestCheck(payRequest);
            //2. Build the payment parameters and call the payment interface to get the result
            PayResponse payResponse;
            PayTypeEnum payTypeEnum = PayTypeEnum.getByCode(payRequest.getPayType());
            switch (payTypeEnum) {
                case AliPay:
                    payResponse = aliPaySendRequest(payRequest);
                    break;
                case WeXinPay:
                    payResponse = weXinPaySendRequest(payRequest);
                    break;
                default:
                    throw new IllegalArgumentException("PayTypeEnum not exist, code=" + payRequest.getPayType());
            }
            //3. If it fails, do extra monitoring (hook method)
            if(! payResponse.getRes()) { monitorFail(payRequest, payResponse); }//4. Record the result
            record(payRequest, payResponse);
            // Other steps...
            return payResponse;
        } catch (RuntimeException e) {
            return new PayResponse(false, e.getMessage(), null); }}/** * Basic parameter check *@param payRequest
     */
    private void requestCheck(PayRequest payRequest) {
        // Check whether the amount is greater than 0
        if(payRequest.getTotalAmount().compareTo(BigDecimal.ZERO) ! =1) {
            throw new RuntimeException("The amount must be greater than 0");
        }
        // Other check...
    }

    private void monitorFail(PayRequest payRequest, PayResponse payResponse) {
        // Monitor failures
        //log... s
    }

    private void record(PayRequest payRequest, PayResponse payResponse) {
        // Record the requested information...
        //PayRecord payRecord = new PayRecord();
    }

    /** ** Pay by alipay */
    private PayResponse aliPaySendRequest(PayRequest payRequest) {
        // Build alipay payment parameters
        AlipayPayRequest alipayPayRequest = new AlipayPayRequest();
        alipayPayRequest.setOrderSn(payRequest.getOrderSn());
        alipayPayRequest.setTotalAmount(payRequest.getTotalAmount());
        / /...

        // Request the alipay interface and return the result
        //AlipayPayResponse alipayPayResponse = call(alipayPayRequest);
        AlipayPayResponse alipayPayResponse = new AlipayPayResponse();
        alipayPayResponse.setTradeNo("2014112400001000340011111118");
        alipayPayResponse.setRes(true);

        return new PayResponse(alipayPayResponse.getRes(), alipayPayResponse.getMsg(), alipayPayResponse.getTradeNo());
    }

    /** * wechat Pay */
    private PayResponse weXinPaySendRequest(PayRequest payRequest) {
        // Build wechat payment parameters
        WexinPayRequest wexinPayRequest = new WexinPayRequest();
        wexinPayRequest.setOrderSn(payRequest.getOrderSn());
        wexinPayRequest.setTotalAmount(payRequest.getTotalAmount());
        / /...

        // Request the wechat interface and return the result
        //WexinPayResponse wexinPayResponse = call(wexinPayRequest);
        WexinPayResponse wexinPayResponse = new WexinPayResponse();
        wexinPayResponse.setTradeNo("2014112400001000340011111118");
        wexinPayResponse.setRes(true);

        return newPayResponse(wexinPayResponse.getRes(), wexinPayResponse.getMsg(), wexinPayResponse.getTradeNo()); }}Copy the code

The PayService class is a little too long. It takes on too much responsibility and violates the single responsibility principle. Moreover, each additional payment method leads to a longer, less readable class, and changes to the rigorously tested process code that violate the open and close principle.

To solve these two problems, we tried to use the policy pattern to optimize.

Optimize the code

Abstract Payment class

Encapsulate some general methods and open extend specific payment methods

public abstract class AbstractPayService {
  
  	abstract PayTypeEnum getPayTypeEnum(a);

    @PostConstruct
    void register(a) {
        PayServiceFactory.register(getPayTypeEnum(), this);
    }

    public abstract PayResponse pay(PayRequest payRequest);

    protected final void requestCheck(PayRequest payRequest) {
        // Check whether the amount is greater than 0
        if(payRequest.getTotalAmount().compareTo(BigDecimal.ZERO) ! =1) {
            throw new RuntimeException("The amount must be greater than 0");
        }
        // Other check...
    }

    protected final void monitorFail(PayRequest payRequest, PayResponse payResponse) {
        // Monitor failures
        //log... s
    }

    protected final void record(PayRequest payRequest, PayResponse payResponse) {
        // Record the requested information...
        //PayRecord payRecord = new PayRecord();}}Copy the code

Specific payment category

Respectively to achieve alipay payment, wechat payment subclasses

/** ** Pay by alipay */
@Service
public class AlipayPayService extends AbstractPayService{
  
  	@Override
    PayTypeEnum getPayTypeEnum(a) {
        return PayTypeEnum.AliPay;
    }

    @Override
    public PayResponse pay(PayRequest payRequest) {
        try {
            // Base check
            requestCheck(payRequest);
            // Build alipay payment parameters and call alipay payment interface to get results
            PayResponse payResponse = sendRequest(payRequest);
            // If this fails, do additional monitoring
            if(! payResponse.getRes()) { monitorFail(payRequest, payResponse); }// Record the result
            record(payRequest, payResponse);
            // Other steps...
            return payResponse;
        } catch (RuntimeException e) {
            return new PayResponse(false, e.getMessage(), null); }}private PayResponse sendRequest(PayRequest payRequest) {
        // Build alipay payment parameters
        AlipayPayRequest alipayPayRequest = new AlipayPayRequest();
        alipayPayRequest.setOrderSn(payRequest.getOrderSn());
        alipayPayRequest.setTotalAmount(payRequest.getTotalAmount());
        / /...

        // Request the alipay interface and return the result
        //AlipayPayResponse alipayPayResponse = call(alipayPayRequest);
        AlipayPayResponse alipayPayResponse = new AlipayPayResponse();
        alipayPayResponse.setTradeNo("2014112400001000340011111118");
        alipayPayResponse.setRes(true);

        return newPayResponse(alipayPayResponse.getRes(), alipayPayResponse.getMsg(), alipayPayResponse.getTradeNo()); }}/** * wechat Pay */
@Service
public class WeXinPayService extends AbstractPayService{

  	@Override
    PayTypeEnum getPayTypeEnum(a) {
        return PayTypeEnum.WeXinPay;
    }
  
    @Override
    public PayResponse pay(PayRequest payRequest) {
        try {
            // Base check
            requestCheck(payRequest);
            // Construct wechat payment parameters and call wechat payment interface
            PayResponse payResponse = sendRequest(payRequest);
            // If this fails, do additional monitoring
            if(! payResponse.getRes()) { monitorFail(payRequest, payResponse); }// Record the result
            record(payRequest, payResponse);
            // Other steps...
            return payResponse;
        } catch (RuntimeException e) {
            return new PayResponse(false, e.getMessage(), null); }}private PayResponse sendRequest(PayRequest payRequest) {
        // Build wechat payment parameters
        WexinPayRequest wexinPayRequest = new WexinPayRequest();
        wexinPayRequest.setOrderSn(payRequest.getOrderSn());
        wexinPayRequest.setTotalAmount(payRequest.getTotalAmount());
        / /...

        // Request the wechat interface and return the result
        //AlipayPayResponse alipayPayResponse = call(alipayPayRequest);
        WexinPayResponse wexinPayResponse = new WexinPayResponse();
        wexinPayResponse.setTradeNo("2014112400001000340011111118");
        wexinPayResponse.setRes(true);

        return newPayResponse(wexinPayResponse.getRes(), wexinPayResponse.getMsg(), wexinPayResponse.getTradeNo()); }}Copy the code

The factory class

public class PayServiceFactory {

    private static final Map<PayTypeEnum, AbstractPayService> payServiceMap = new HashMap<>();

    public static void register(PayTypeEnum payTypeEnum, AbstractPayService payService) {
        payServiceMap.put(payTypeEnum, payService);
    }

    public static AbstractPayService get(PayTypeEnum payTypeEnum) {
        returnpayServiceMap.get(payTypeEnum); }}Copy the code

After the optimization of the strategy mode, when adding new payment methods, the original code can also be basically unchanged, and each payment method of the code is independent, the code looks cleaner. However, have you found that the logic of the Pay method in wechat payment class and Alipay payment class is the same, but the logic of calling the external interface in sendRequest method is inconsistent? We tried to use the template method mode for further optimization.

define

The Template Method pattern is defined as follows: define the skeleton of an algorithm in an operation and defer some steps of the algorithm to a subclass so that the subclass can redefine specific steps of the algorithm without changing the structure of the algorithm. It’s a behavioral pattern.

Pattern structure

The template method pattern contains the following primary roles.

The abstract template

Abstract template class, responsible for giving the outline and skeleton of an algorithm. It consists of a template method and several basic methods. These methods are defined as follows.

① Template method: defines the skeleton of the algorithm, in a certain order to call the basic methods contained in it.

② Basic method: is a step in the whole algorithm, including the following types.

  • Abstract methods: declared in abstract classes and implemented by concrete subclasses.
  • Concrete methods: They are already implemented in abstract classes and can be inherited or overridden in concrete subclasses, but the Richter substitution principle instructs us not to overwrite non-abstract methods of parent classes.
  • Hook methods: Implemented in abstract classes, including logical methods for judgment and empty methods that need to be overridden by subclasses.

The specific implementation

Concrete implementation classes, which implement abstract methods and hook methods defined in abstract classes, are part of a top-level logic.

UML diagrams

Basic pattern implementation

Abstract template classes

// An abstract class with hook methods
abstract class HookAbstractClass {
    // Template method
    public void TemplateMethod(a) {
        abstractMethod1();
        HookMethod1();
        if (HookMethod2()) {
            SpecificMethod();
        }
        abstractMethod2();
    }
    // Specific methods
    public void SpecificMethod(a) {
        System.out.println("A concrete method in an abstract class is called...");
    }
    // Hook method 1
    public void HookMethod1(a) {}// Hook method 2
    public boolean HookMethod2(a) {
        return true;
    }
    // Abstract method 1
    public abstract void abstractMethod1(a);
    // Abstract method 2
    public abstract void abstractMethod2(a);
}
Copy the code

Concrete subclass

// Contains a concrete subclass of the hook method
class HookConcreteClass extends HookAbstractClass {
    public void abstractMethod1(a) {
        System.out.println("The implementation of abstract method 1 is called...");
    }
    public void abstractMethod2(a) {
        System.out.println("The implementation of abstract method 2 is called...");
    }
    public void HookMethod1(a) {
        System.out.println("Hook method 1 overridden...");
    }
    public boolean HookMethod2(a) {
        return false; }}Copy the code

The test class

public class HookTemplateMethod {
    public static void main(String[] args) {
        HookAbstractClass tm = newHookConcreteClass(); tm.TemplateMethod(); }}Copy the code

Since hook method 2 returns false, SpecificMethod in the abstract class is not called and is executed as follows

The implementation of abstract method 1 is called... Hook method 1 overridden... The implementation of abstract method 2 is called...Copy the code

If the code for hook methods HookMethod1() and HookMethod2() changes, the result of running the program changes too.

Optimize payment methods

Define the payment method enumeration

Same as before

Define an abstract payment class

The Pay method is a template method that defines a set of logic and leaves the sendRequest method open to subclasses to override, leaving the rest unchanged.

public abstract class AbstractPayService {

    abstract PayTypeEnum getPayTypeEnum(a);

    @PostConstruct
    final void register(a) {
        PayServiceFactory.register(getPayTypeEnum(), this);
    }

    /** * Payment template method *@param payRequest
     * @return* /
    public final PayResponse pay(PayRequest payRequest) {
        try {
            // Base check
            requestCheck(payRequest);
            // Build the payment parameters and call the payment interface to get the results
            PayResponse payResponse = sendRequest(payRequest);
            // If it fails, do extra monitoring (hook method)
            if(! payResponse.getRes()) { monitorFail(payRequest, payResponse); }// Record the result
            record(payRequest, payResponse);
            // Other steps...
            return payResponse;
        } catch (RuntimeException e) {
            return new PayResponse(false, e.getMessage(), null); }}private void requestCheck(PayRequest payRequest) {
        // Check whether the amount is greater than 0
        if(payRequest.getTotalAmount().compareTo(BigDecimal.ZERO) ! =1) {
            throw new RuntimeException("The amount must be greater than 0");
        }
        // Other check...
    }

    private void monitorFail(PayRequest payRequest, PayResponse payResponse) {
        // Monitor failures
        //log... s
    }

    private void record(PayRequest payRequest, PayResponse payResponse) {
        // Record the requested information...
        //PayRecord payRecord = new PayRecord();
    }

    /** * Abstract payment request method *@param payRequest
     * @return* /
    abstract protected PayResponse sendRequest(PayRequest payRequest);
}

Copy the code

Define a concrete payment class

Implement sendRequest separately.

@Service
public class AlipayPayService extends AbstractPayService {

    @Override
    PayTypeEnum getPayTypeEnum(a) {
        return PayTypeEnum.AliPay;
    }

    @Override
    protected PayResponse sendRequest(PayRequest payRequest) {
        // Build alipay payment parameters
        AlipayPayRequest alipayPayRequest = new AlipayPayRequest();
        alipayPayRequest.setOrderSn(payRequest.getOrderSn());
        alipayPayRequest.setTotalAmount(payRequest.getTotalAmount());
        / /...

        // Request the alipay interface and return the result
        //AlipayPayResponse alipayPayResponse = call(alipayPayRequest);
        AlipayPayResponse alipayPayResponse = new AlipayPayResponse();
        alipayPayResponse.setTradeNo("2014112400001000340011111118");
        alipayPayResponse.setRes(true);

        return newPayResponse(alipayPayResponse.getRes(), alipayPayResponse.getMsg(), alipayPayResponse.getTradeNo()); }}@Service
public class WexinPayService extends AbstractPayService {

    @Override
    PayTypeEnum getPayTypeEnum(a) {
        return PayTypeEnum.WeXinPay;
    }

    @Override
    protected PayResponse sendRequest(PayRequest payRequest) {
        // Build wechat payment parameters
        WexinPayRequest wexinPayRequest = new WexinPayRequest();
        wexinPayRequest.setOrderSn(payRequest.getOrderSn());
        wexinPayRequest.setTotalAmount(payRequest.getTotalAmount());
        / /...

        // Request the wechat interface and return the result
        //AlipayPayResponse alipayPayResponse = call(alipayPayRequest);
        WexinPayResponse wexinPayResponse = new WexinPayResponse();
        wexinPayResponse.setTradeNo("2014112400001000340011111118");
        wexinPayResponse.setRes(true);

        return newPayResponse(wexinPayResponse.getRes(), wexinPayResponse.getMsg(), wexinPayResponse.getTradeNo()); }}Copy the code

Package Factory class

Same as before

Advantages, disadvantages and applications of patterns

advantages

  • It encapsulates the immutable and extends the mutable. It encapsulates the algorithm which is considered to be the invariable part into the parent class, which is convenient for code reuse, and inherits the algorithm of the variable part by the subclass, which is convenient for the subclass to continue to expand.
  • Each subclass is only responsible for its own extended functionality, in accordance with the single responsibility principle.
  • Some methods are implemented by subclasses, so subclasses can be extended to add functionality in accordance with the open closed principle.
  • If the number of subclasses is controlled properly, the code will look cleaner.

disadvantages

  • Each implementation needs to define a subclass, which will lead to an increase in the number of classes and a more abstract design. Too many classes will indirectly increase the complexity of the system implementation.
  • Abstract methods in the parent class are implemented by the subclass, and the results of the execution of the subclass affect the results of the parent class, leading to a reverse control structure that makes code more difficult to read.
  • Due to the disadvantages of inheritance, if the parent class adds a new abstract method, all subclasses need to change it.

Applicable scenario

  • When the overall steps of the algorithm are very fixed, but some parts of the algorithm are mutable, the template method pattern can be used to abstract the mutable parts out for subclass implementation. Invariant parts are kept in the parent class to avoid code duplication.
  • When you need to control the extension of a subclass, the template method calls hook operations only at certain points, allowing extension only at those points.

Pattern source code application

  • MyBatis source BaseExecutor implementation, a basic SQL execution class, the implementation of most SQL execution logic, and then several methods to subclass customization to complete, mainly provides the basic functions of cache management and transaction management.
  • Servlet source HttpServlet implementation, service method for template method, according to the request forward to need to subclass doPost, doGet and other methods to do the next step.
  • Spring AbstractApplicationContext implementation in the source code, refresh method, defines the bean initialization process, for different subclasses form such as annotations or XML forms have different implementations.
  • .

conclusion

The template method pattern is similar to the policy pattern, but the scenarios are different. The template method has a higher degree of abstraction, which abstracts some fixed things to avoid code duplication and other problems. Flexible use of hook functions can effectively control unnecessary subclass extension.


reference

  • C.biancheng.net/view/1376.h…
  • Segmentfault.com/a/119000001…
  • Opendocs.alipay.com/open/02ivbs…

Welcome to like, comment, share, your support is my biggest motivation, forwarding trouble note the source ~