preface

This series of articles refer to “Zen of Design Patterns”, cainiaotuo.com and some articles on the Internet to summarize, and develop their own applications. The design pattern is named in zen of Design Pattern.

Design pattern is only a summary of coding skills of some developers in daily development and is not fixed. It can be extended and applied according to the actual situation of the project business, and must not be bound by this. Don’t use it just for the sake of using it. Design patterns are a double-edged sword, and excessive design can lead to less readable code and larger code.

This series will not cover the seven Principles of design patterns in detail, nor will it categorize design patterns. This will only increase the cost of learning and memory, and can also lead to when using the idea of curing, always want to such a design is in line with the principle of xx, xx design pattern is, what is xx model type, and so on, not want to see in this series, only one goal, combined with the application of daily development to share, to provide a kind of code optimization.

Learning and forgetting may be the best way.

Just as the saying goes: there is no way in the world, but when more people walk, it becomes a way. In my opinion, design pattern is the same, it is not a law, but the experience of the predecessors, we learn and apply, rather than mechanically copy.

define

Official: Define a set of algorithms, encapsulate each algorithm, and make them interchangeable.

Human: there is a common logical interface or abstract class, and there is a control class to implement the common logical interface to control the class, in reference to know which interface to call the policy class.

Application scenarios

Sample 1

For those of you who have never seen design patterns before, here is a calculator model that is widely available on the web.

In a simple calculator we need to focus on the calculator operators. Let’s assume this calculator is very, very simple and supports only addition, subtraction, multiplication and division.

Start by defining an operator interface.

Public interface IOperator {/** ** calculate logic * @return result */ int doOperator(int num1, int num2); }Copy the code

The code is relatively simple, only do the calculation of plastic.

Addition implementation:

public class AddOperator implements IOperator{ @Override public int doOperator(int num1, int num2) { return num1 + num2; }}Copy the code

Multiplication implementation:

public class MultiplyOperator implements IOperator{ @Override public int doOperator(int num1, int num2) { return num1 * num2; }}Copy the code

I’m not going to write subtraction and division, just refer to the above.

These IOperator implementations are different operational strategies. Policy alone is not enough, we need a control class to manage it uniformly. All references are implemented through this management class. There are many ways to implement management classes, don’t limit yourself to one or two online demos.

A:

The policy (IOperator) is injected into the management class attributes through the constructor, and then executed by calling exec directly.

The demo is equivalent to building an additive control class and then performing the parameter and operation.

Public class StrategyManager_1 {private final IOperator operator; public StrategyManager_1(IOperator operator) { this.operator = operator; } public int exec(int num1, int num2){ return operator.doOperator(num1, num2); Public static void main(String[] args) {AddOperator operator = new AddOperator();} public static void main(String[] args) {AddOperator operator = new AddOperator(); StrategyManager_1 manager_1 = new StrategyManager_1(operator); int exec = manager_1.exec(1, 2); System.out.println(exec); }}Copy the code

Method 2:

You can also use the switch statement to perform matching calculations with the input operator.

public class StrategyManager_2 { public static int exec(int num, int num1, String operator) { switch (operator) { case "+" : return new AddOperator().doOperator(num, num1); case "*" : return new MultiplyOperator().doOperator(num, num1); default: return 0; } } public static void main(String[] args) { StrategyManager_2.exec(1, 2, "+"); }}Copy the code

This approach does not require the user to build the policy object. It is similar to the simple factory pattern, just passing in an identity. It is easier to call and I recommend this approach. In practical development, it is not recommended to let users encapsulate policy objects themselves, which increases the learning cost of callers and may cause various unexpected errors. Consider that future generations are building on top of you, and they don’t know or need to know about your policy objects, because policy objects encapsulate your business and consumers don’t.

Would a real calculator require the user to create a policy object AddOperator that handles addition? Obviously not, the user is only concerned with the entered numbers, operators, and results.

There may be many ways to achieve this, for you to discover.

The sample 2

Let me share with you how to write a policy-mode interface in a SpringBoot project.

Recently, an open interface was written to accept user information synchronized from other projects. However, the structure and content of user information synchronized from different projects are different, and some need to be customized for business development.

As an example, let’s say I have two projects (A and B) that need to synchronize data to me.

Condition:

1. Only user name and mobile phone number were imported into project A, and only ID card and name were imported into project B.

2. In project A, SMS should be sent synchronously after acceptance. In project B, age and birthday should be calculated according to ID card.

3. Logs must be recorded before and after synchronization.

1. Create an entry object:

@data public Class ReceiveBean {private String name; /** * source */ private int source; } @equalSandHashCode (callSuper = true) @data Public Class BReceiveBean extends ReceiveBean{private String idNo; } @equalSandHashCode (callSuper = true) @data Public Class AReceiveBean extends ReceiveBean{private String phone; }Copy the code

Instead of displaying get/set methods, lombok annotations are used in entity beans.

There is a key source field in ReceiveBean that identifies the source of the channel and can be used in other ways instead.

2. Define top-level policy interfaces

Public interface IReceiveService {/** * The accepted parameter is a string in JSON format * converted by the caller * @param bean input parameter */ void receive(ReceiveBean bean);  }Copy the code

Using a string or Map object provides better compatibility, or the conversion can be done internally, passing the conversion to the caller for external references.

3. Interface implementation at the abstract level

@SuppressWarnings("unused") public abstract class AbstractReceiveService implements IReceiveService{ ReceivePre (ReceiveBean bean) {// receivePre(ReceiveBean bean) {// receivePre(ReceiveBean bean) { You can add a map parameter if you want, Protected void receiveAfter(ReceiveBean bean) {// Logging} // Need to be overwritten, each type of custom service protected Abstract void doAction(ReceiveBean bean); @override public void Receive (ReceiveBean bean) {// Operation before service processing receivePre(bean); // The business starts processing doAction(bean); ReceiveAfter (bean); }}Copy the code

Abstract class internal implementation of receive method, here depends on the specific business, although there are customized services, but the overall logic of the same can be so implemented, if the difference is large, you can override the receive method in the subclass, self-implementation, otherwise the default to go the same process.

ReceivePre and receiveAfter are called for processing before and after services. Both methods save logs. These are default operations. This can affect subsequent processing. The doAction method can be used as an abstract method that requires subclasses to implement their own business processing operations. It can also provide a default implementation like the receivePre and receiveAfter methods.

Take A look at the implementation class of one of the A projects:

@Service public class AReceiveService extends AbstractReceiveService{ @Override protected void doAction(ReceiveBean AReceiveBean ab = (AReceiveBean) bean; } @Override protected void receivePre(ReceiveBean bean) { super.receivePre(bean); AReceiveBean AB = (AReceiveBean) bean; // Perform other operations while recording logs. } @Override protected void receiveAfter(ReceiveBean bean) { super.receiveAfter(bean); AReceiveBean AB = (AReceiveBean) bean; }}Copy the code

Internal optional override method, if not necessary.

4. The control class

@SuppressWarnings("unused") public enum ReceiveSourceEnum { A(1, "aReceiveService") { @Override public ReceiveBean coverJson(String json) { return JSON.parseObject(json, AReceiveBean.class); } }, B(2, "bReceiveService") { @Override public ReceiveBean coverJson(String json) { return JSON.parseObject(json, BReceiveBean.class); }}; private final int source; private final String serviceName; Public Abstract ReceiveBean coverJson(String json); Public static ReceiveSourceEnum getWithSource(int source) {for (ReceiveSourceEnum sourceEnum: ReceiveSourceEnum.values()) { if (sourceEnum.getSource() == source) { return sourceEnum; }} throw new DemoException(" Source type does not exist!" ); } public static IReceiveService getReceiveService(int source) {ReceiveSourceEnum sourceEnum = getWithSource(source); Assert.notNull(sourceEnum, "source is null"); IReceiveService receiveService = (IReceiveService)SpringContextUtil.getBean(sourceEnum.getServiceName()); Assert.notNull(sourceEnum, "service is null"); return receiveService; } ReceiveSourceEnum(int source, String serviceName) { this.source = source; this.serviceName = serviceName; } public int getSource() { return source; } public String getServiceName() { return serviceName; }}Copy the code

ReceiveSourceEnum is used as the control class. Two static methods getReceiveService and getWithSource are provided internally to obtain the service and enumeration types.

An abstract method, coverJson, is defined in the enumeration class for converting jSON-formatted strings into objects. Because the accepted parameters are json format strings, this can improve the expansibility of the interface, and the internal conversion into the corresponding parameter entity class is convenient for subsequent operations.

GetReceiveService internally retrieves beans from the Spring container by service name. This way the service can be invoked statically and the method is simple.

There are many ways to do this online, but here is only one:

@Component public class SpringContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringContextUtil.applicationContext = applicationContext; } public static applicationContext getApplicationContext() {return applicationContext; Public static Object getBean(String name) {return getApplicationContext().getBean(name); Public static <T> T getBean(class <T> clazz) {return getApplicationContext().getBean(clazz); } public static <T> T getBean(String name, String name, String name) Class<T> clazz) { return getApplicationContext().getBean(name, clazz); }}Copy the code

5. Reference method

Unit tests using SpringBoot.

Note: Do not use the main function directly, otherwise you will find that the ApplicationContext in SpringContextUtil will always be null due to classLoader.

@RunWith(SpringRunner.class) @SpringBootTest public class TestBean { @Test public void testContextUtil() { ReceiveBean bean = new ReceiveBean(); bean.setName("123"); bean.setSource(1); String paramStr = JSON.toJSONString(bean); ReceiveBean receiveBean = coverJsonToBean(paramStr); ReceiveSourceEnum.getReceiveService(receiveBean.getSource()).receive(receiveBean); } private static ReceiveBean coverJsonToBean(String json){ JSONObject jo = JSONObject.parseObject(json); if (! Jo.containskey ("source")) {throw new DemoException(" passed argument missing :source"); } // risk of strong error int source = (int)jo.get("source"); ReceiveSourceEnum sourceEnum = ReceiveSourceEnum.getWithSource(source); return sourceEnum.coverJson(json); }}Copy the code

But it also shows how simple the call is for the consumer. If C projects or other projects are connected, you only need to extend the AbstractReceiveService class and ReceiveSourceEnum types.

Here is only one of my ideas, a reference, there must be other better way to achieve.

UML diagrams

Pictures from the rookie tutorial. (Lazy = =)

summary

The policy pattern was first understood in order to optimize the large number of if-else statements in the code. Although it can be implemented through switch statements and enum classes in some cases, it does not scale well, and the code is scattered in the current business class, which can be barely seen when there are few contacts. More often, you will find that a large number of custom businesses are integrated within a single entry method, which is difficult to read and maintain, and the business class becomes large. And the lack of notes makes maintenance a nightmare.

Under the strategy mode, the common methods extracted from different channels are placed in the parent class, and the customized business is realized in their respective subclasses, so that the overall process is clear and the business class will not expand indefinitely. As long as the modification and expansion do not involve the public part, it can be changed without worry, and the customized business of each channel is relatively independent.

If the common part is completely unavailable to the receiver, don’t worry; you can override the policy method directly, rewrite the business logic, and still guarantee the same entry.