Talk about common design patterns in work

Posted by kitegirl on Mon, 01 Nov 2021 07:57:16 +0100

Usually we write code. In most cases, we write code in a pipeline, and we can basically realize the business logic. How to find fun in writing code? I think the best way is to use design patterns to optimize your business code. Today, I want to talk to you about what design patterns I have used in my daily work.

1. Strategy mode

1.1 business scenario

Suppose there is such a business scenario, the big data system pushes files over and adopts different parsing methods according to different types. Most small partners will write the following code:

if(type=="A"){
   //Parse in A format
 
}else if(type=="B"){
    //Parse in B format
}else{
    //Parse in default format
}

What are the possible problems with this code?
If there are more branches, the code here will become bloated, difficult to maintain and low readability.
If you need to access a new parsing type, you can only modify it on the original code.
To put it professionally, the above code violates the opening and closing principle and single principle of object-oriented programming.
Open close principle (open for extension, but closed for modification): adding or deleting a logic needs to be modified to the original code
Single principle (which stipulates that a class should have only one reason for change): to modify any type of branch logic code, you need to change the code of the current class.
If your code is maozi: there are multiple conditional branches such as if...else, and each conditional branch can be encapsulated and replaced, we can use the policy pattern to optimize.

1.2 definition of policy mode

The policy pattern defines algorithm families and encapsulates them separately so that they can be replaced with each other. This pattern makes the changes of the algorithm independent of the customers using the algorithm. Is the definition of this policy pattern a little abstract? Let's look at some common and easy to understand metaphors:
Suppose you date a little sister with different personality types and use different strategies. Some invite movies, some eat snacks, and some go shopping. Of course, the purpose is to get the little sister's heart. Please watch movies, eat snacks and go shopping are different strategies.
The policy pattern encapsulates each algorithm into a separate class with a common interface for a group of algorithms, so that they can be replaced with each other.

1.3 use of strategy mode

How to use the policy mode? Realized by maozi:
An interface or abstract class with two methods (one method matches the type and one replaceable logical implementation method)
Differentiated implementation of different policies (that is, implementation classes of different policies)
Use policy mode

1.3.1 one interface, two methods
public interface IFileStrategy {
    
    //Which file resolution type does it belong to
    FileTypeResolveEnum gainFileType();
    
    //Encapsulated common algorithm (specific analysis method)
    void resolve(Object objectparam);
}
1.3.2 differentiated implementation of different strategies

Specific implementation of type A policy

@Component
public class AFileResolve implements IFileStrategy {
    
    @Override
    public FileTypeResolveEnum gainFileType() {
        return FileTypeResolveEnum.File_A_RESOLVE;
    }

    @Override
    public void resolve(Object objectparam) {
      logger.info("A Type resolution file, parameters:{}",objectparam);
      //Specific logic of type A parsing
    }
}

Specific implementation of type B policy

@Component
public class BFileResolve implements IFileStrategy {
   
    @Override
    public FileTypeResolveEnum gainFileType() {
        return FileTypeResolveEnum.File_B_RESOLVE;
    }


    @Override
    public void resolve(Object objectparam) {
      logger.info("B Type resolution file, parameters:{}",objectparam);
      //B type parsing specific logic
    }
}

Specific implementation of default type policy

@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 resolution file, parameters:{}",objectparam);
      //Specific logic of default type resolution
    }
}
1.3.3 usage strategy mode

How to use it? With the help of the spring life cycle, we use the ApplicationContextAware interface to initialize the matching policies into the map. Then provide the resolveFile method externally.

/**
 *  @author Little boy picking snails
 */
@Component
public class StrategyUseService 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);
        }
    }

    //Put different strategies into map
    @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));
    }
}

2. Responsibility chain model

2.1 business scenario

Let's take a look at a common business scenario, placing orders. The basic logic of the order placement interface generally includes non empty parameter verification, security verification, blacklist verification, rule interception, etc. Many partners use exceptions to implement:

public class Order {

    public void checkNullParam(Object param){
      //Parameter non null verification
      throw new RuntimeException();
    }
    public void checkSecurity(){
      //Security verification
      throw new RuntimeException();
    }
    public void checkBackList(){
        //Blacklist verification
        throw new RuntimeException();
    }
    public void checkRule(){
        //Rule interception
        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");
        }
    }
}

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, but can not return more fields. At this time, you need to customize the exception class.
Moreover, Alibaba's development manual stipulates that it is forbidden to make logical judgment with exceptions.
[mandatory] exceptions should not be used for process control and condition control. Note: the original intention of exception design is to solve various unexpected situations in program operation, and the efficiency of exception handling is much lower than that of condition judgment.
How to optimize this code? The responsibility chain model can be considered

2.2 definition of responsibility chain mode

When you want more than one object to have a chance to process a request, use the responsibility chain model.
The responsibility chain pattern creates a chain of receiver objects for requests. There are multiple object nodes in the execution chain. Each object node has the opportunity (condition matching) to process the request transaction. If an object node is processed, it can be passed to the next node to continue processing or return to processing according to the actual business requirements. This mode gives the type of request and decouples the sender and receiver of the request.
The responsibility chain pattern is actually a pattern of processing a request, which gives multiple processors (object nodes) the opportunity to process the request until one of them is successful. In the responsibility chain mode, multiple processors are chained, and then requests are passed on the chain:

An analogy:

Suppose you go to an elective course in the evening and sit in the last row in order to walk a little. When you come to the classroom, you find several beautiful little sisters sitting in front of you, so you find a note and write: "Hello, can you be my girlfriend? If you don't want to, please pass it forward". The notes were passed one by one, and then to the sister in the first row. She handed the note to the teacher. It was said that the teacher was unmarried in her 40s

2.3 use of responsibility chain mode

How to use the responsibility chain model?
An interface or abstract class
Differentiated processing of each object
Object chain (array) initialization (concatenation)

2.3.1 an interface or abstract class

This interface or abstract class requires:
There is a property that points to the next object
A set method that sets the next object
The method of differentiated implementation for subclass objects (such as the doFilter method of the following code)

/**
 * Little boy picking snails
 */
public abstract class AbstractHandler {

    //The next object in the responsibility chain
    private AbstractHandler nextHandler;

    /**
     * The next object in the responsibility chain
     */
    public void setNextHandler(AbstractHandler nextHandler){
        this.nextHandler = nextHandler;
    }

    /**
     * Specific parameters intercept logic and implement it for subclasses
     */
    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);

}
2.3.2 differentiated processing of each object

In the responsibility chain, the differentiated processing of each object, such as the business scenario in this section, includes parameter verification object, security verification object, blacklist verification object and rule interception object

/**
 * Parameter verification object
 **/
@Component
@Order(1) //Ranked first in order, verified first
public class CheckParamFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        System.out.println("Non null parameter check");
    }
}

/**
 *  Security verification object
 */
@Component
@Order(2) //The verification sequence is the second
public class CheckSecurityFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        //invoke Security check
        System.out.println("Security call verification");
    }
}
/**
 *  Blacklist verification object
 */
@Component
@Order(3) //The verification sequence is the third
public class CheckBlackFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        //invoke black list check
        System.out.println("Verification blacklist");
    }
}

/**
 *  Rule intercept object
 */
@Component
@Order(4) //The verification sequence is No. 4
public class CheckRuleFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        //check rule
        System.out.println("check rule");
    }
}
2.3.3 linking objects (initialization) & & using
@Component("ChainPatternDemo")
public class ChainPatternDemo {

    //Automatically inject objects into each responsibility chain
    @Autowired
    private List<AbstractHandler> abstractHandleList;

    private AbstractHandler abstractHandler;

    //spring is automatically executed after injection, and the objects of the responsibility chain are connected
    @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);
            }
        }
    }

    //Call this method directly
    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;
    }
}

The operation results are as follows:

Non null parameter check
 Security call verification
 Verification blacklist
check rule

3. Template method mode

3.1 business scenario

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). Follow a similar process as follows:

A request will go through these processes:

  • Query merchant information
  • Sign the request message
  • Send http request out
  • Check and sign the returned message

Here, some merchants may go out through agents, and some may go through direct links. Assuming that merchants A and B are currently connected, many partners may realize this. The pseudo code is as follows:

// Merchant A processing handle
CompanyAHandler implements RequestHandler {
   Resp hander(req){
   //Query merchant information
   queryMerchantInfo();
   //Countersign
   signature();
   //http request (merchant A assumes an agent)
   httpRequestbyProxy()
   //Signature verification
   verify();
   }
}
// Merchant B processing handle
CompanyBHandler implements RequestHandler {
   Resp hander(Rreq){
   //Query merchant information
   queryMerchantInfo();
   //Countersign
   signature();
   // http request (merchant B does not go through the agent and is directly connected)
   httpRequestbyDirect();
   // Signature verification
   verify(); 
   }
}

Suppose you add a new C merchant access, you need to implement another set of such code. Obviously, this code repeats some common methods, but rewrites this method in each subclass.
How to optimize it? You can use the template method pattern.

3.2 template method mode definition

Define the skeleton process of an algorithm in operation, and delay some steps to subclasses, so that subclasses can redefine some specific steps of an algorithm without changing the structure of an algorithm. Its core idea is to define a series of steps of an operation. For some steps that cannot be determined temporarily, it is left to subclasses to implement, so that different subclasses can define different steps.
A popular analogy:
For example, when chasing a girlfriend, you should first "hold hands", then "hug", then "kiss", and then "pat... Er... Hand". It doesn't matter whether you hold it with your left hand or your right hand, but in the whole process, you set a process template and follow the template.

3.3 use of formwork

An abstract class that defines the skeleton process (abstract methods together)
Determine the common method steps and put them into the abstract class (remove the abstract method tag)
For uncertain steps, the subclass is de differentiated
Let's continue the business process example of the above example and use the template method to optimize it, ha:

3.3.1 an abstract class that defines the skeleton process

Because the process of each request is as follows:
Query merchant information
Sign the request message
Send http request out
Check and sign the returned message
Therefore, we can define an abstract class that contains several methods of the request process. Methods are first defined as abstract methods:

/**
 * The abstract class defines the skeleton process (query merchant information, signature addition, http request, signature verification)
 */
abstract class AbstractMerchantService  { 

      //Query merchant information
      abstract queryMerchantInfo();
      //Countersign
      abstract signature();
      //http request
      abstract httpRequest();
       // Signature verification
       abstract verifySinature();
 
}
3.3.2 determine the common method steps and put them into the abstract class
abstract class AbstractMerchantService  { 

     //Template method flow
     Resp handlerTempPlate(req){
           //Query merchant information
           queryMerchantInfo();
           //Countersign
           signature();
           //http request
           httpRequest();
           // Signature verification
           verifySinature();
     }
      // Whether Http takes proxy (provided to subclass Implementation)
      abstract boolean isRequestByProxy();
}
3.3.3 for uncertain steps, the subclass is de differentiated

Because it is uncertain whether to go through the proxy process, it is given to subclasses to implement it.
Merchant A's request realization:

CompanyAServiceImpl extends AbstractMerchantService{
    Resp hander(req){
      return handlerTempPlate(req);
    }
    //http proxy
    boolean isRequestByProxy(){
       return true;
    }
Merchant B Request fulfillment:
CompanyBServiceImpl extends AbstractMerchantService{
    Resp hander(req){
      return handlerTempPlate(req);
    }
    //Company B does not act as an agent
    boolean isRequestByProxy(){
       return false;
    }

4. Observer mode

4.1 business scenario

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 registers successfully, we send a message or an email to the user. Therefore, we often have the following code:

void register(User user){
  insertRegisterUser(user);
  sendIMMessage();
  sendEmail();
}

What's wrong with this code? If the product needs more: now the user who has successfully registered will send a short message to the user. So you have to change the code of the register method again... Does this violate the opening and closing principle.

void register(User user){
  insertRegisterUser(user);
  sendIMMessage();
  sendMobileMessage();
  sendEmail();
}

Moreover, if the interface for sending SMS fails, does it affect the user registration again?! At this time, do you have to add an asynchronous method to notify the message...
In fact, we can use observer pattern optimization.

4.2 observer mode definition

Observer pattern defines a one to many dependency between objects. When the state of an object changes, all objects that depend on it are notified and updated.
Observer mode belongs to behavior mode. When the state of an object (observed) changes, all dependent objects (observer objects) will be notified and broadcast. Its main members are the observer and the observed.
Observable: target object. All observers will be notified when the state changes.
observer: accept the status change notification of the observed and execute the pre-defined business.
Usage scenario: the scenario of asynchronous notification after completing something. For example, log in successfully, send an IM message, etc.

4.3 use of observer mode

If the observer mode is implemented, it is still relatively simple.
An observable class;
Multiple observers;
Realization of observer differentiation
Classic observer mode encapsulation: EventBus actual combat

4.3.1 class observable of one Observer and Observer of multiple 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 observer
   public void addServer(Observer observer){
      observers.add(observer);      
   }
   
   //Remove observer
   public void removeServer(Observer observer){
      observers.remove(observer);      
   }
   //notice
   public void notifyAllObservers(int state){
      if(state!=1){
          System.out.println(""Is not the status of the notification");
         return ;
      }
   
      for (Observer observer : observers) {
         observer.doEvent();
      }
   }  
}
4.3.2 realization of observer differentiation
//Observer
interface Observer {  
   void doEvent();  
}  
//Im message
IMMessageObserver implements Observer{
   void doEvent(){
      System.out.println("send out IM news");
   }
}

//SMS
MobileNoObserver implements Observer{
   void doEvent(){
      System.out.println("Send SMS message");
   }
}
//EmailNo
EmailObserver implements Observer{
   void doEvent(){
      System.out.println("send out email news");
   }
}
4.3.3 EventBus actual combat

It's still a little troublesome to make a set of observer mode code yourself. In fact, Guava EventBus is encapsulated. It provides a set of annotation based event bus, and the api can be used flexibly.
Let's take a look at the actual code of EventBus. First, we can declare an EventBusCenter class, which is similar to the role of observer above.

public class EventBusCenter {

    private static EventBus eventBus = new EventBus();

    private EventBusCenter() {
    }

    public static EventBus getInstance() {
        return eventBus;
    }
     //Add observer
    public static void register(Object obj) {
        eventBus.register(obj);
    }
    //Remove observer
    public static void unregister(Object obj) {
        eventBus.unregister(obj);
    }
    //Push the message to the observer
    public static void post(Object obj) {
        eventBus.post(obj);
    }
}

Then declare the observer EventListener

public class EventListener {

    @Subscribe //Add a subscription, and mark this method here as an event handling method  
    public void handle(NotifyEvent notifyEvent) {
        System.out.println("send out IM news" + notifyEvent.getImNo());
        System.out.println("Send SMS message" + notifyEvent.getMobileNo());
        System.out.println("send out Email news" + notifyEvent.getEmailNo());
    }
}

//Notification event class
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;
    }
 }

Test with demo:

public class EventBusDemoTest {

    public static void main(String[] args) {

        EventListener eventListener = new EventListener();
        EventBusCenter.register(eventListener);
        EventBusCenter.post(new NotifyEvent("13372817283", "123@qq.com", "666"));
        }
}

Operation results:

send out IM Message 666
 Send SMS message 13372817283
 send out Email Message 123@qq.com

5. Factory mode

5.1 business scenario

Factory mode is generally used together with strategy mode. Used to optimize a large number of if...else... Or switch...case... Conditional statements.
Let's take the example of the policy pattern in the first section. Create different parsing objects according to 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;
 }

In fact, this is the factory pattern, which defines an interface for creating objects and lets its subclasses decide which factory class to instantiate. The factory pattern delays the creation process to the subclasses.
For the example of strategy mode, instead of using the previous code, we have built a factory mode with the help of the characteristics of spring. Ha ha, my friends can go back to the example and taste it. I'll move the code down and my friends will taste it again:

/**
 *  @author Little boy picking snails
 */
@Component
public class StrategyUseService implements ApplicationContextAware{

    private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();

    //Put all the objects parsed by the file type into the map. When you need to use it, you can use it at your fingertips. This is an embodiment of the factory model
    @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));
    }
}

5.2 using factory mode

It is also relatively simple to define the factory mode:
A factory interface that provides a method for creating different objects.
Its subclasses implement factory interfaces and construct different objects
Use factory mode

5.3.1 one factory interface
interface IFileResolveFactory{
   void resolve();
}
5.3.2 factory interfaces implemented by different subclasses
class AFileResolve implements IFileResolveFactory{
   void resolve(){
      System.out.println("file A Type resolution");
   }
}

class BFileResolve implements IFileResolveFactory{
   void resolve(){
      System.out.println("file B Type resolution");
   }
}

class DefaultFileResolve implements IFileResolveFactory{
   void resolve(){
      System.out.println("Default file type resolution");
   }
}
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();

In general, for factory mode, you won't see the above code. The factory pattern will appear together with other design patterns, such as strategy pattern.

6. Singleton mode

6.1 business scenario

Singleton mode ensures that a class has only one instance and provides a global access point to access it. The connection between I/O and database is generally realized in singleton mode. The Task Manager in Windows is also a typical singleton mode.
Let's look at an example of singleton mode

/**
 * Little boy picking snails
 */
public class LanHanSingleton {

    private static LanHanSingleton instance;

    private LanHanSingleton(){

    }

    public static LanHanSingleton getInstance(){
        if (instance == null) {
            instance = new LanHanSingleton();
        }
        return instance;
    }

}

The above example is a lazy singleton implementation. It's lazy to create instances when they need to be used. If yes, it will be returned. If not, it will be created. The synchronized keyword needs to be added. Otherwise, there may be a linear security problem.

6.2 classic writing of singleton mode

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

6.2.1 hungry man mode
public class EHanSingleton {

   private static EHanSingleton instance = new EHanSingleton();
   
   private EHanSingleton(){      
   }

   public static EHanSingleton getInstance() {
       return instance;
   }
   
}

The hungry man mode is hungry and diligent. The instance has been built at the time of initialization. Whether you use it later or not, create a good instance first. There is no thread safety problem, but it wastes memory space.

6.2.2 double check lock
public class DoubleCheckSingleton {

   private volatile static DoubleCheckSingleton instance;

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

The single instance mode of double check lock combines the advantages and disadvantages of lazy and hungry. In the above code example, 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 internal class
public class InnerClassSingleton {

   private static class InnerClassSingletonHolder{
       private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
   }

   private InnerClassSingleton(){}
   
   public static final InnerClassSingleton getInstance(){
       return InnerClassSingletonHolder.INSTANCE;
   }
}

The implementation method of static internal class is a bit similar to 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 needs to delay initialization.

6.2.4 enumeration
public enum SingletonEnum {

    INSTANCE;
    public SingletonEnum getInstance(){
        return INSTANCE;
    }
}

Enumeration implementation of a single example, the code is concise and clear. It also automatically supports serialization mechanism to absolutely prevent multiple instantiations.

Article source: https://mp.weixin.qq.com/s/2jWM4O_1pS7r9P5-1U6cjw