The author | careful (leaf)

A Strategy Pattern defines a set of policies that are encapsulated in different classes, each of which can be interchangeable according to the current scenario, so that changes in policies can be independent of the operator. For example, we want to go somewhere, depending on the distance (or according to the financial situation at hand) to choose different ways of travel (bike-sharing, bus, Didi taxi, etc.), these travel methods are different strategies.

When to use policy mode

Ali development protocol – programming protocol – control statement – sixth: more than three layers of if-else logic judgment code can be used to defend the statement, policy mode, state mode, etc. I’m sure you’ve all seen this code:

If (conditionA) {logic 1} else if (conditionB) {logic 2} else if (conditionC) {logic 3} else {logic 4}Copy the code

This code, while simple to write, clearly violates two basic principles of object orientation:

  • Single responsibility principle (a class should have only one reason to change) : because any subsequent changes to any logic will change the current class

  • Open for extension, closed for modification: If logic needs to be added (removed) at this point, the original code will inevitably have to be modified

Because of the violation of these two principles, especially when the amount of code in the if-else block is large, subsequent code expansion and maintenance becomes increasingly difficult and error-prone. Therefore, based on my experience, I think it is a good practice to:

  • If -else no more than 2 layers, 1 to 5 lines of code in the block, write directly to the block, otherwise encapsulated as a method

  • If -else more than 2 levels, but the block of code in no more than 3 lines, try to use guard statements

  • If -else more than 2 layers, and more than 3 lines of code in the block, use policy mode whenever possible

Use the strategy pattern happily

There are many ways to implement the strategy pattern in Spring, so here I share my current “best practices” for implementing the strategy pattern (if you have better practices, please let me know and discuss them).

Demand background

Our platform’s dynamic forms were previously dedicated to the submission of model input. Now the business side wants to open up the form capability, not only for model submission, but also for the submission of business-side specified functionality (designed to bind to an HSF generalization service, which is the RPC framework within the system). With the addition of the “preview mode” submission we made when configuring the form, the form now has the following three types of submission:

  • Submit when previewing the form

  • Submission of model input

  • Submit when binding HSF

And now, my best routine.

First, define the policy interface

First define the interface for the policy, including two methods:

1. Method of obtaining the policy type

2. Methods of handling policy logic

/** * Form Submission handler */ Public Interface FormSubmitHandler<R extends Serializable> {/** * Get the submission type (return value can also use an existing enumeration class) ** @return Submission type */ String getSubmitType(); /** * Handle the form submission request ** @param request ** @return response, left for the prompt returned to the front end, Right for service value */ CommonPairResponse<String, R> handleSubmit(FormSubmitRequest Request); }Copy the code
Public class FormSubmitRequest {/** * @see FormSubmitHandler#getSubmitType() */ private String submitType; /** * userId */ private Long userId; / / Private Map<String, Object> formInput; // Other attributes}Copy the code

The getSubmitType method of FormSubmitHandler is used to obtain the form submission type (that is, the policy type), which is used to directly obtain the corresponding policy implementation according to the parameters passed by the client. The parameters passed by the client are encapsulated as a FormSubmitRequest and passed to handleSubmit for processing.

The second step is the implementation of relevant policies

Submit when previewing the form

@Component public class FormPreviewSubmitHandler implements FormSubmitHandler<Serializable> { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public String getSubmitType() { return "preview"; } @Override public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest Request) {logger.info(" Preview mode commit: userId={}, formInput={}", request.getUserId(), request.getFormInput()); Return CommonPairResponse. Success (" preview mode to submit data succeed!" , null); }}Copy the code

Submission of model input

@Component public class FormModelSubmitHandler implements FormSubmitHandler<Long> { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public String getSubmitType() { return "model"; } @override public CommonPairResponse<String, Long> handleSubmit(FormSubmitRequest Request) {logger.info(" Model submission: userId={}, formInput={}", request.getUserId(), request.getFormInput()); Long modelId = createModel(request); Long modelId = createModel(request); Return CommonPairResponse. Success (" model submitted to success!" , modelId); } private Long createModel(FormSubmitRequest request) {// createModel logic return 123L; }}Copy the code

HSF mode submission

@Component public class FormHsfSubmitHandler implements FormSubmitHandler<Serializable> { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public String getSubmitType() { return "hsf"; } @Override public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest Request) {logger.info(" submit in HSF mode: userId={}, formInput={}", request.getUserId(), request.getFormInput()); CommonPairResponse<String, Serializable> Response = hsfSubmitData(Request); return response; }... }Copy the code

Step three, set up a simple factory for the strategy

@Component public class FormSubmitHandlerFactory implements InitializingBean, ApplicationContextAware { private static final Map<String, FormSubmitHandler<Serializable>> FORM_SUBMIT_HANDLER_MAP = new HashMap<>(8); private ApplicationContext appContext; Public FormSubmitHandler<Serializable> public FormSubmitHandler<Serializable> getHandler(String submitType) { return FORM_SUBMIT_HANDLER_MAP.get(submitType); } @override public void afterPropertiesSet() {// Register all FormSubmitHandler in Spring with FORM_SUBMIT_HANDLER_MAP appContext.getBeansOfType(FormSubmitHandler.class) .values() .forEach(handler -> FORM_SUBMIT_HANDLER_MAP.put(handler.getSubmitType(), handler)); } @Override public void setApplicationContext(@NonNull ApplicationContext applicationContext) { appContext = applicationContext; }}Copy the code

Let’s make the FormSubmitHandlerFactory implement the InitializingBean interface. In the afterPropertiesSet method, Automatically register all formSubmithandlers with the FORM_SUBMIT_HANDLER_MAP based on the Spring container, so that when the Spring container is started, The getHandler method gets the corresponding form submission handler directly from the submitType.

Step 4, use & test

In the form service, we use the FormSubmitHandlerFactory to get the corresponding form submission handler to handle different types of submissions:

@Service public class FormServiceImpl implements FormService { @Autowired private FormSubmitHandlerFactory submitHandlerFactory; public CommonPairResponse<String, Serializable> submitForm(@NonNull FormSubmitRequest request) { String submitType = request.getSubmitType(); / / according to find corresponding submitType submit processor FormSubmitHandler < Serializable > submitHandler = submitHandlerFactory. GetHandler (submitType);  / / determine whether submitType corresponding handler exists if (submitHandler = = null) {return CommonPairResponse. Failure (" illegal submit type: " + submitType); } / / processing submit return submitHandler. HandleSubmit (request). }}Copy the code

The Factory is only responsible for obtaining the Handler, the Handler is only responsible for handling the specific submission, and the Service is only responsible for the logic orchestration, so as to achieve the “low coupling and high cohesion” in function.

Write a simple Controller:

@RestController public class SimpleController { @Autowired private FormService formService; @PostMapping("/form/submit") public CommonPairResponse<String, Serializable> submitForm(@RequestParam String submitType, @RequestParam String formInputJson) { JSONObject formInput = JSON.parseObject(formInputJson); FormSubmitRequest request = new FormSubmitRequest(); request.setUserId(123456L); request.setSubmitType(submitType); request.setFormInput(formInput); return formService.submitForm(request); }}Copy the code

Finally, a simple test:

I feel it. It’s a very fluid feeling

Imagine an extension

If we need to add a new policy, such as binding the FaaS submission, we simply add a new policy implementation:

@Component public class FormFaasSubmitHandler implements FormSubmitHandler<Serializable> { private final Logger logger =  LoggerFactory.getLogger(this.getClass()); @Override public String getSubmitType() { return "faas"; } @Override public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest Request) {logger.info("FaaS mode commit: userId={}, formInput={}", request.getUserId(), request.getFormInput()); CommonPairResponse<String, Serializable> Response = faasSubmitData(Request); return response; }... }Copy the code

You don’t need to change any code at this point, because when the Spring container restarts, it automatically registers FormFaasSubmitHandler with the FormSubmitHandlerFactory