preface

Hello, everyone. I am a little boy picking up field snails.

Usually when we write code, most of the cases are pipelined code, basic business logic can be realized. The best way to find fun in writing code, I think, is to use design patterns to optimize your business code. Today I want to talk to you about some of the design patterns I use in my daily work.

  • Dry goods public number: a little boy picking up snails

1. Policy mode

1.1 Service Scenarios

Suppose you have a business scenario where a big data system pushes files and parses them differently depending on the type. Most people would write code like this:

If (type = = "A") {/ / in accordance with A format analytic} else if (type = = "B") {/ / press B format analytic} else {/ / in accordance with the default format analytic}Copy the code

What might be the problem with this code?

  • If there are more branches, the code here will become bloated, difficult to maintain, and less readable.
  • If you need to add a new parsing type, you can only change it on top of the original code.

Technically speaking, the above code violates the open and close principles of object-oriented programming and the single principle.

  • The open closed principle (open for extension, but closed for modification) : adding or removing logic requires modifying the original code
  • Rule of uniformity (which states that a class should have only one reason for change) : Changing any type of branching logic requires changing the code of the current class.

If your code has multiple ifs… Else conditional branches and so on, and each conditional branch can be encapsulated as an alternative, and we can use the policy pattern to optimize.

1.2 Definition of Policy Patterns

The policy pattern defines families of algorithms, encapsulates them so that they are interchangeable, and makes changes to the algorithm independent of the customers using the algorithm. Isn’t the definition of this policy pattern a bit abstract? Let’s look at some simple metaphors:

If you are dating different types of girls, you should use different strategies. Some movies are good, some snacks are good, and some shopping is best. Of course, the purpose is to win the little sister’s heart, watching movies, eating snacks, shopping are different strategies.

The policy pattern takes a set of algorithms and encapsulates each algorithm into a separate class with a common interface, making them interchangeable.

1.3 Using policy Mode

How do you use the policy pattern? Maroon achieved:

  • An interface or abstract class with two methods (a method matching type and an alternative logical implementation method)
  • Differential implementation of different policies (that is, implementation classes of different policies)
  • Using the Policy Pattern

1.3.1 One interface, two methods

Public interface IFileStrategy {// Which file parsing type it belongs to FileTypeResolveEnum gainFileType(); Void resolve(Object objectParam); }Copy the code

1.3.2 Differential implementation of different strategies

Type A policies are implemented

@Component public class AFileResolve implements IFileStrategy { @Override public FileTypeResolveEnum gainFileType() { return FileTypeResolveEnum.File_A_RESOLVE; } @override public void resolve(objectParam) {logger.info(" objectParam: {}", objectParam); }}Copy the code

Implementation of type B policies

@Component public class BFileResolve implements IFileStrategy { @Override public FileTypeResolveEnum gainFileType() { return FileTypeResolveEnum.File_B_RESOLVE; } @override public void resolve(Object objectParam) {logger.info(" objectParam: {}", objectParam); }}Copy the code

The default type policy is implemented

@Component public class DefaultFileResolve implements IFileStrategy { @Override public FileTypeResolveEnum gainFileType() { return FileTypeResolveEnum.File_DEFAULT_RESOLVE; } @override public void resolve(Object objectParam) {logger.info(" default type resolver, parameter: {}", objectParam); // The default type resolves the logic}}Copy the code

1.3.3 Using policy Mode

How do you use it? We use the Spring lifecycle to initialize the appropriate policy into the Map using the ApplicationContextAware interface. Then provide the resolveFile method externally.

/** * @author @Component public class implements ApplicationContextAware{private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>(); public void resolveFile(FileTypeResolveEnum fileTypeResolveEnum, Object objectParam) { IFileStrategy iFileStrategy = iFileStrategyMap.get(fileTypeResolveEnum); if (iFileStrategy ! = null) { iFileStrategy.resolve(objectParam); Throws map@override public void setApplicationContext(ApplicationContext) throws BeansException { Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class); tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService)); }}Copy the code

2. Chain of responsibility mode

2.1 Service Scenarios

Let’s look at a common business scenario, placing an order. Order interface, basic logic, generally have parameters non-empty check, security check, blacklist check, rule block and so on. Many partners use exceptions to implement:

Public class Order {public void checkNullParam(Object param){// Throw new RuntimeException(); } public void checkSecurity(){throw new RuntimeException(); } public void checkBackList(){throw new RuntimeException(); } public void checkRule(){throw new RuntimeException(); } public static void main(String[] args) { Order order= new Order(); try{ order.checkNullParam(); order.checkSecurity (); order.checkBackList(); order2.checkRule(); System.out.println("order success"); }catch (RuntimeException e){ System.out.println("order fail"); }}}Copy the code

This code uses exceptions to judge logical conditions. If the subsequent logic becomes more and more complex, some problems will occur: for example, exceptions can only return exception information, not more fields, so you need to customize the exception class.

Moreover, The Ali development manual stipulates that it is forbidden to use exceptions to make logical judgments.

[Mandatory] Exceptions should not be used for process control or condition control.

Note: The original intention of exception design is to solve all kinds of unexpected situations in program operation, and the efficiency of exception processing is much lower than the condition judgment method.

How do you optimize this code? Consider the chain of responsibility model

2.2 Definition of liability chain mode

Use the chain of responsibility pattern when you want to give more than one object a chance to handle a request.

The chain of responsibility pattern creates a chain of recipient objects for the request. There are multiple object nodes on the execution chain, and each object node has the opportunity (conditional matching) to process the requested transaction. If an object node is finished processing, it can be passed to the next node to continue processing or return to the next node according to the actual business needs. This pattern decouples the sender and receiver of the request by giving the type of request.

The chain of responsibility pattern is actually a pattern for processing a request, giving multiple processors (object nodes) the opportunity to process the request until one of them succeeds. The chain of responsibility pattern chains multiple processors and then passes requests along the chain:

Here’s an analogy:

Let’s say you go to an elective class at night and sit in the back row so you can walk a little. Came to the classroom, found the front sat several beautiful little sister, so you find a piece of paper, write: “Hello, can be my girlfriend? If you don t want to, please send it to us. The notes went up one by one, then to the first row of the girl hands, she gave the note to the teacher, I heard the teacher in his 40s unmarried…

2.3 Use of responsibility chain mode

How to use the chain of responsibility model?

  • An interface or abstract class
  • Each object is treated differently
  • Chain of objects (arrays) initialization (concatenation)

2.3.1 An interface or abstract class

This interface, or abstract class, needs to:

  • There is a property that points to the next object of responsibility
  • A set method that sets the next object
  • Methods for differentiating subclass objects (such as the doFilter method in the following code)
Private AbstractHandler AbstractHandler; private AbstractHandler AbstractHandler; Public void setNextHandler(AbstractHandler nextHandler){this.nexthAndler = nextHandler; */ public void filter(Request Request, Response Response) {doFilter(Request, Response); if (getNextHandler() ! = null) { getNextHandler().filter(request, response); } } public AbstractHandler getNextHandler() { return nextHandler; } abstract void doFilter(Request filterRequest, Response response); }Copy the code

2.3.2 Each object is treated differently

In the responsibility chain, each object is processed differently. For example, the service scenario in this section includes parameter verification object, security verification object, blacklist verification object, and rule blocking object

/** */ @component@order (1) Public class CheckParamFilterObject extends AbstractHandler {@override public void doFilter(Request Request, Response Response) {system.out.println (" non-null argument check "); }} / security checking object * / * * * @ Component @ Order (2) / / check Order 2 public class CheckSecurityFilterObject extends AbstractHandler { @Override public void doFilter(Request request, Response Response) {//invoke Security check system.out.println (" Security check "); }} public class CheckBlackFilterObject extends AbstractHandler {}} public class CheckBlackFilterObject extends AbstractHandler { @Override public void doFilter(Request request, //invoke black list check system.out.println (" check blacklist "); }} public class CheckRuleFilterObject extends AbstractHandler {}} public class CheckRuleFilterObject extends AbstractHandler { @Override public void doFilter(Request request, Response response) { //check rule System.out.println("check rule"); }}Copy the code

2.3.3 Object chain linking (initialization) && use

@Component("ChainPatternDemo") Public Class ChainPatternDemo {Autowired private List<AbstractHandler> abstractHandleList; private AbstractHandler abstractHandler; @postconstruct public void initializeChainFilter(){for(int I = 0; i<abstractHandleList.size(); i++){ if(i == 0){ abstractHandler = abstractHandleList.get(0); }else{ AbstractHandler currentHander = abstractHandleList.get(i - 1); AbstractHandler nextHander = abstractHandleList.get(i); currentHander.setNextHandler(nextHander); }}} public Response exec(Request Request, Response Response) {abstracthandler. filter(Request, Response);  return response; } public AbstractHandler getAbstractHandler() { return abstractHandler; } public void setAbstractHandler(AbstractHandler abstractHandler) { this.abstractHandler = abstractHandler; }}Copy the code

The running results are as follows:

Non-empty parameter Check Security call verify Verify blacklist check ruleCopy the code

3. Template method pattern

3.1 Service Scenarios

Suppose we have such a business scenario: different merchants in the internal system call our system interface to interact with the external third-party system (HTTP). Go through a process like this, as follows:

A request will go through these steps:

  • Querying Merchant Information
  • The request message is signed
  • Send an HTTP request
  • Check the returned packets

Here, some merchants may be acting out, have a plenty of go straight. Assuming that A and B merchants are currently connected, many partners may be implemented in this way, with the pseudo-code as follows:

CompanyAHandler implements RequestHandler {Resp hander(req){queryMerchantInfo(); / / the signature of the signature (s); HttpRequestbyProxy () // verify(); CompanyBHandler implements RequestHandler {Resp hander(Rreq){queryMerchantInfo(); / / the signature of the signature (s); HttpRequestbyDirect (); // httpRequestbyDirect(); / / attestation verify (); }}Copy the code

Assuming you add a new C merchant access, you need to implement another set of these codes. Obviously, the code repeats itself, some generic method, but rewrites that method in every subclass.

How do you optimize it? You can use the template method pattern.

3.2 Template method pattern definition

Define the skeleton flow of an algorithm in an operation and defer steps to subclasses so that subclasses can redefine specific steps of an algorithm without changing the structure of that algorithm. The idea is to define a sequence of steps for an operation, and leave it to subclasses to implement some of the steps that can’t be determined at the moment, so that different subclasses can define different steps.

To use a popular metaphor:

Model example: To pursue a girlfriend, first “hold hands”, then “hug”, then “kiss”, then “pat.. The forehead.. Hand “. As for the specific you use the left hand or the right hand, it doesn’t matter, but the whole process, set a process template, according to the template to the line.

3.3 Using template Methods

  • An abstract class that defines skeleton flow (abstract methods put together)
  • Identified common method steps, put into abstract classes (remove abstract method tags)
  • Uncertain steps for subclasses to de-differentiate implementation

Let’s continue with the business process example above and use the template method to optimize:

3.3.1 An abstract class that defines skeleton processes

Because each request goes through the following steps:

  • Querying Merchant Information
  • The request message is signed
  • Send an HTTP request
  • Check the returned packets

So we can define an abstract class that contains several methods of the request flow. The methods are first defined as abstract methods.

Abstract class AbstractMerchantService {abstract queryMerchantInfo(); abstract class abstractmerchantInfo (); // add signature(); // abstract httpRequest(); Abstract verifySinature(); }Copy the code

3.3.2 Identified common method steps, placed in abstract classes

Abstract Class AbstractMerchantService {Resp handlerTempPlate(req){queryMerchantInfo(); / / the signature of the signature (s); / / HTTP request httpRequest (); / / attestation verifySinature (); } // whether Http goes to proxy (provided to subclass implementation) abstract Boolean isRequestByProxy(); }Copy the code

3.3.3 Uncertain steps, to subclasses to achieve differentiation

Since it is uncertain whether to go through the proxy process, subclasses implement it.

Realization of merchant A’s request:

CompanyAServiceImpl extends AbstractMerchantService{ Resp hander(req){ return handlerTempPlate(req); } // go to HTTP proxy Boolean isRequestByProxy(){return true; }Copy the code

Merchant B’s request realizes:

CompanyBServiceImpl extends AbstractMerchantService{ Resp hander(req){ return handlerTempPlate(req); } // company B is not going to go proxy Boolean isRequestByProxy(){return false; }Copy the code

4. Observer mode

4.1 Service Scenarios

Login and registration should be the most common business scenario. Take registration as an example, we often encounter similar scenarios, that is, after the user has registered successfully, we send a message to the user, or send an email and so on, so there are often the following code:

Void register(User User){insertRegisterUser (User); sendIMMessage(); SendEmail (); }Copy the code

What’s wrong with this piece of code? If the product also adds requirements: now registered successful users, and then send a short message to the user notification. So you have to change the code of the register method again… I don’t know if that violates the open close rule.

Void register(User User){insertRegisterUser (User); sendIMMessage(); SendMobileMessage (); SendEmail (); }Copy the code

And, if the SMS interface fails, does it affect the user registration? ! At this point, is it necessary to add an asynchronous method to the notification message is good…

In fact, we can use observer mode optimization.

4.2 Observer pattern definition

The observer pattern defines a one-to-many dependency between objects, and when an object’s state changes, all dependent objects are notified and business updates are completed.

The observer mode is a behavior mode in which the state of an object (observed) changes, and all dependent objects (observer objects) are notified and broadcast notifications. Its main members are the observer and the observed.

  • Observerable: A target object that notifies all observers when its status changes.
  • Observer: Receives notifications of state changes from the observed and performs predefined services.

Usage scenario: Asynchronous notification scenario when something is done. For example, login successfully, send an IM message and so on.

4.3 Use observer mode

The observer mode implementation is relatively simple.

  • An observed class Observerable;
  • Observer C.
  • Differential realization of the observer
  • Classic Observer mode package: EventBus Combat

4.3.1 An observed class Observerable and multiple Observer observers

public class Observerable { private List<Observer> observers = new ArrayList<Observer>(); private int state; public int getState() { return state; } public void setState(int state) { notifyAllObservers(); } // add observers public void addServer(Observer Observer){observers.add(Observer); } // observerserver public void observerserver (Observer Observer){observers.remove(Observer); } // public void notifyAllObservers(int state){if(state! =1){system.out.println (" not notification status "); return ; } for (Observer observer : observers) { observer.doEvent(); }}}Copy the code

4.3.2 Differential realization of observers

// Interface Observer {void doEvent(); } // IMMessageObserver implements Observer{void doEvent () {system.out.println (" implements Im messages "); } // MobileNoObserver implements Observer{void doEvent () {system.out.println (" implements an SMS message "); } //EmailNo EmailObserver implements Observer{void doEvent () {system.out.println (" EmailObserver implements Observer "); }}Copy the code

4.3.3 EventBus of actual combat

Making your own code for observer mode is a bit of a hassle. In fact, Guava EventBus encapsulates this by providing an annotation-based EventBus with an API that can be used flexibly.

We can declare an EventBusCenter class, which is similar to the Observerable of the observed role.

public class EventBusCenter { private static EventBus eventBus = new EventBus(); private EventBusCenter() { } public static EventBus getInstance() { return eventBus; } public static void register(Object obj) {eventbus.register (obj); } public static void unregister(Object obj) {eventbus.unregister (obj); Public static void post(Object obj) {eventbus.post (obj); }}Copy the code

The observer EventListener is then declared

Public class EventListener {@subscribe // Public void Handle (NotifyEvent NotifyEvent) {system.out.println (" send IM message "+ notifyevent.getimno ()); System.out.println(" send SMS message "+ notifyevent.getMobileno ()); System.out.println(" Send Email message "+ notifyevent.getemailno ()); }} public class NotifyEvent {private String mobileNo; private String emailNo; private String imNo; public NotifyEvent(String mobileNo, String emailNo, String imNo) { this.mobileNo = mobileNo; this.emailNo = emailNo; this.imNo = imNo; }}Copy the code

Using demo tests:

public class EventBusDemoTest { public static void main(String[] args) { EventListener eventListener = new EventListener(); EventBusCenter.register(eventListener); EventBusCenter.post(new NotifyEvent("13372817283", "[email protected]", "666")); }}Copy the code

Running results:

Sending an IM message 666 Sending an SMS message 13372817283 Sending an Email message [email protected]Copy the code

5. Factory mode

5.1 Service Scenarios

The factory pattern is typically used in conjunction with the policy pattern. To optimize a large number of if… else… Or the switch… case… Conditional statement.

Let’s take the example of the policy pattern from section 1. Create different parsing objects for different file parsing types

 
 IFileStrategy getFileStrategy(FileTypeResolveEnum fileType){
     IFileStrategy  fileStrategy ;
     if(fileType=FileTypeResolveEnum.File_A_RESOLVE){
       fileStrategy = new AFileResolve();
     }else if(fileType=FileTypeResolveEnum.File_A_RESOLV){
       fileStrategy = new BFileResolve();
     }else{
       fileStrategy = new DefaultFileResolve();
     }
     return fileStrategy;
 }
Copy the code

This is the factory pattern, which defines an interface to create an object and lets its subclasses decide which factory class to instantiate. The factory pattern delays the creation of the factory class until the subclass does.

In this example, we did not use the previous code. Instead, we made a factory mode with the help of Spring’s features.

/** * @author @Component public class implements ApplicationContextAware{private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>(); // Place all file type parsed objects in the map and use them whenever needed. @override public void setApplicationContext(ApplicationContext ApplicationContext) throws BeansException {  Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class); tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService)); }}Copy the code

5.2 Using factory Mode

Defining the factory pattern is also relatively simple:

  • A factory interface that provides a method for creating different objects.
  • Its subclasses implement factory interfaces and construct different objects
  • Using factory Mode

5.3.1 A factory interface

interface IFileResolveFactory{
   void resolve();
}
Copy the code

5.3.2 Different subclasses implement factory interfaces

Class AFileResolve implements IFileResolveFactory{void resolve(){system.out.println (" implements "); }} class BFileResolve implements IFileResolveFactory{void resolve(){system.out.println (); }} class implements IFileResolveFactory{void resolve(){system.out.println (" default file type "); }}Copy the code

5.3.3 Using factory Mode

// Construct different factory objects IFileResolveFactory fileResolveFactory; If (fileType= "A"){fileResolveFactory = new AFileResolve(); }else if(fileType= "B"){fileResolveFactory = new BFileResolve(); }else{ fileResolveFactory = new DefaultFileResolve(); } fileResolveFactory.resolve();Copy the code

Normally, you won’t see this code for factory mode. Factory patterns will appear in conjunction with other design patterns such as policy patterns.

6. Singleton mode

6.1 Service Scenarios

The singleton pattern ensures that a class has only one instance and provides a global access point to access it. I/O and database connection, generally with the singleton mode to achieve DE. Task Manager in Windows is also a typical singleton.

Take a look at an example of the singleton pattern

/** * public class LanHanSingleton {private static LanHanSingleton instance; private LanHanSingleton(){ } public static LanHanSingleton getInstance(){ if (instance == null) { instance = new LanHanSingleton(); } return instance; }}Copy the code

The above example is a lazy singleton implementation. It is lazy to create instances only when they are needed. If yes, return; if no, create a new one. Add the synchronized keyword; otherwise, there may be linear security problems.

6.2 Classical writing method of singleton mode

In fact, there are several implementations of singleton mode, such as hanhan-hungry mode, double check lock, static inner class, enumeration and so on.

6.2.1 Hungry mode

public class EHanSingleton { private static EHanSingleton instance = new EHanSingleton(); private EHanSingleton(){ } public static EHanSingleton getInstance() { return instance; }}Copy the code

Hungry mode, it is more hungry, more diligent, the instance has been created at the time of initialization, regardless of whether you need to use later, first create a new instance again. This is not a thread-safe problem, but it is a waste of memory.

6.2.2 Double check Lock

public class DoubleCheckSingleton { private static DoubleCheckSingleton instance; private DoubleCheckSingleton() { } public static DoubleCheckSingleton getInstance(){ if (instance == null) { synchronized (DoubleCheckSingleton.class) { if (instance == null) { instance = new DoubleCheckSingleton(); } } } return instance; }}Copy the code

The singleton pattern of double checklock implementation combines the advantages and disadvantages of lazy and hungry. In the above code examples, a layer of if condition judgment is added inside and outside the synchronized keyword, which not only ensures thread safety, but also improves execution efficiency and saves memory space compared with direct locking.

6.2.3 Static inner Classes

public class InnerClassSingleton { private static class InnerClassSingletonHolder{ private static final InnerClassSingleton INSTANCE = new InnerClassSingleton(); } private InnerClassSingleton(){} public static final InnerClassSingleton getInstance(){ return InnerClassSingletonHolder.INSTANCE; }}Copy the code

The static inner class is implemented somewhat like a double check lock. However, this method is only applicable to static domain scenarios. The double-check lock method can be used when the instance domain requires delayed initialization.

6.2.4 enumeration

public enum SingletonEnum { INSTANCE; public SingletonEnum getInstance(){ return INSTANCE; }}Copy the code

Enumeration of singleton, simple and clear code. It also automatically supports serialization, which absolutely prevents multiple instantiations.