After optimizing the business code with design patterns, my joy is back

Usually we write code. In most cases, we write code in a pipelined way , and basically we can implement 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 will talk with you about which 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 the file, and adopts different analysis methods according to different types. Most of the small partners will write the following code:

if(type=="A"){
   //按照A格式解析
 
}else if(type=="B"){
    //按B格式解析
}else{
    //按照默认格式解析
}
复制代码

What could be wrong with this code ?

  • If there are more branches, the code here will become bloated, difficult to maintain, and less readable .
  • If you need to access a new parsing type, you can only modify the original code .

To put it professionally, the above code violates the open-closed principle and the single principle of object-oriented programming .

  • Open-closed principle (open for extension, but closed for modification): adding or deleting a logic requires modification to the original code
  • Single principle (specifying that a class should have only one reason for change): Modifying any type of branching logic code requires changing the code of the current class.

If your code is purple: there are multiple if...else and other conditional branches, and each conditional branch can be encapsulated and replaced, we can use the strategy pattern to optimize.

1.2 Definition of Strategy Pattern

The strategy pattern defines a family of algorithms and encapsulates them separately so that they can be replaced with each other. This pattern makes the changes of the algorithm independent of the clients using the algorithm. Isn't the definition of this strategy pattern a bit abstract? So let's look at a simple analogy:

Suppose you are dating young ladies of different personality types, you should use different strategies, some are better for movies, some are good for snacks, and some are best for shopping. Of course, the purpose is to win the heart of the young lady. Watching movies, eating snacks, and shopping are different strategies.

The strategy pattern targets a set of algorithms, encapsulating each algorithm into a separate class with a common interface, making them interchangeable.

1.3 Use of strategy mode

How to use strategy mode? Jiang Zi achieved:

  • An interface or abstract class with two methods in it (one method matching the type, one alternative logic implementation method)
  • Differentiated implementations of different strategies (that is, implementation classes of different strategies)
  • Use strategy mode

1.3.1 One interface, two methods

public interface IFileStrategy {
    
    //属于哪种文件解析类型
    FileTypeResolveEnum gainFileType();
    
    //封装的公用算法(具体的解析方法)
    void resolve(Object objectparam);
}

复制代码

1.3.2 Differentiated Implementation of Different Strategies

A-type strategy specific implementation

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

    @Override
    public void resolve(Object objectparam) {
      logger.info("A 类型解析文件,参数:{}",objectparam);
      //A类型解析具体逻辑
    }
}
复制代码

Specific implementation of type B strategy

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


    @Override
    public void resolve(Object objectparam) {
      logger.info("B 类型解析文件,参数:{}",objectparam);
      //B类型解析具体逻辑
    }
}
复制代码

Implementation of default type strategy

@Component
public class DefaultFileResolve implements IFileStrategy {

    @Override
    public FileTypeResolveEnum gainFileType() {
        return FileTypeResolveEnum.File_DEFAULT_RESOLVE;
    }

    @Override
    public void resolve(Object objectparam) {
      logger.info("默认类型解析文件,参数:{}",objectparam);
      //默认类型解析具体逻辑
    }
}
复制代码

1.3.3 Using Strategy Mode

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

/**
 *  @author 公众号:捡田螺的小男孩
 */
@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);
        }
    }

    //把不同策略放到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. Chain of Responsibility Model

2.1 Business Scenario

Let's look at a common business scenario, placing an order. The order interface, the basic logic, generally includes parameter non-null check, security check, blacklist check, rule interception, etc. Many partners will use exceptions to achieve:

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");
        }
    }
}

复制代码

This code uses exceptions to make logical condition judgments. If the subsequent logic becomes more and more complex, some problems will arise: if the exception can only return exception information, but cannot return more fields, then you need to customize the exception class .

In addition, 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 various unexpected situations in program operation, and the processing efficiency of exception is much lower than that of condition judgment.

How to optimize this code? Consider the Chain of Responsibility Model

2.2 Definition of Chain of Responsibility Model

The Chain of Responsibility pattern is used when you want to give more than one object a chance to be able to handle a request .

The Chain of Responsibility pattern creates a chain of receiver objects for the request. There are multiple object nodes on the execution chain, and 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 be processed according to actual business requirements. This pattern gives the type of request, decoupling the sender and receiver of the request.

The Chain of Responsibility pattern is actually a pattern of processing a request, which gives multiple processors (object nodes) a chance to process the request until one of them succeeds. The Chain of Responsibility pattern chain multiple processors into a chain, and then let the request pass on the chain:

To make an analogy:

Suppose you go to an elective in the evening and sit in the last row so that you can walk a bit. When you come to the classroom, you find several beautiful young ladies 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 forward it." The notes were passed on one by one, and later passed to the girl in the first row. She handed the notes to the teacher. I heard that the teacher was in her 40s and unmarried...

2.3 Use of Chain of Responsibility Model

How to use the Chain of Responsibility model?

  • An interface or abstract class
  • Differentiation for each object
  • Object chain (array) initialization (connection)

2.3.1 An interface or abstract class

This interface or abstract class requires:

  • has a property that points to the next object
  • A set method that sets the next object
  • A method to differentiate the implementation of subclass objects (such as the doFilter method of the following code)
/**
 *  关注公众号:捡田螺的小男孩
 */
public abstract class AbstractHandler {

    //责任链中的下一个对象
    private AbstractHandler nextHandler;

    /**
     * 责任链的下一个对象
     */
    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);

}
复制代码

2.3.2 Differential processing of each object

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

/**
 * 参数校验对象
 **/
@Component
@Order(1) //顺序排第1,最先校验
public class CheckParamFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        System.out.println("非空参数检查");
    }
}

/**
 *  安全校验对象
 */
@Component
@Order(2) //校验顺序排第2
public class CheckSecurityFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        //invoke Security check
        System.out.println("安全调用校验");
    }
}
/**
 *  黑名单校验对象
 */
@Component
@Order(3) //校验顺序排第3
public class CheckBlackFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        //invoke black list check
        System.out.println("校验黑名单");
    }
}

/**
 *  规则拦截对象
 */
@Component
@Order(4) //校验顺序排第4
public class CheckRuleFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        //check rule
        System.out.println("check rule");
    }
}
复制代码

2.3.3 Object chaining (initialization) && use

@Component("ChainPatternDemo")
public class ChainPatternDemo {

    //自动注入各个责任链的对象
    @Autowired
    private List<AbstractHandler> abstractHandleList;

    private AbstractHandler abstractHandler;

    //spring注入后自动执行,责任链的对象连接起来
    @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;
    }
}
复制代码

The results are as follows:

非空参数检查
安全调用校验
校验黑名单
check rule
复制代码

3. Template method pattern

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 mode). Go through a process similar to this, as follows:

A request will go through the following processes:

  • Query business information
  • Sign the request message
  • send http request out
  • Check the returned message

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


// 商户A处理句柄
CompanyAHandler implements RequestHandler {
   Resp hander(req){
   //查询商户信息
   queryMerchantInfo();
   //加签
   signature();
   //http请求(A商户假设走的是代理)
   httpRequestbyProxy()
   //验签
   verify();
   }
}
// 商户B处理句柄
CompanyBHandler implements RequestHandler {
   Resp hander(Rreq){
   //查询商户信息
   queryMerchantInfo();
   //加签
   signature();
   // http请求(B商户不走代理,直连)
   httpRequestbyDirect();
   // 验签
   verify(); 
   }
}

复制代码

Assuming that a new C merchant is added, you need to implement another set of such codes. Obviously, this code is repeated , some common methods are rewritten in each subclass .

How to optimize it? Template method pattern can be used .

3.2 Template method pattern definition

Define the skeleton flow of an algorithm in an operation, and defer some steps to subclasses, allowing subclasses to redefine certain steps of an algorithm without changing its structure. Its core idea is to define a series of steps for an operation. For some steps that cannot be determined temporarily, leave it to subclasses to implement, so that different subclasses can define different steps.

To make a simple analogy:

Pattern example: To chase a girlfriend, you must first "hold hands", then "hug", then "kiss", and then "pat... er... hand". As for whether you use the left hand or the right hand, it doesn't matter, but for the whole process, a process template is set, and you can follow the template.

3.3 Template method usage

  • An abstract class that defines the skeleton process (abstract methods are put together)
  • Determined common method steps, put into abstract class (remove abstract method tag)
  • Uncertain steps, de-differentiated implementation for subclasses

Let's continue the business process example above, and use the template method to optimize it together:

3.3.1 An abstract class that defines the skeleton process

Because the process of each request is the following steps:

  • Query business information
  • Sign the request message
  • send http request out
  • Check the returned message

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

/**
 * 抽象类定义骨架流程(查询商户信息,加签,http请求,验签)
 */
abstract class AbstractMerchantService  { 

      //查询商户信息
      abstract queryMerchantInfo();
      //加签
      abstract signature();
      //http 请求
      abstract httpRequest();
       // 验签
       abstract verifySinature();
 
}

复制代码

3.3.2 Determine the common method steps and put them in the abstract class

abstract class AbstractMerchantService  { 

     //模板方法流程
     Resp handlerTempPlate(req){
           //查询商户信息
           queryMerchantInfo();
           //加签
           signature();
           //http 请求
           httpRequest();
           // 验签
           verifySinature();
     }
      // Http是否走代理(提供给子类实现)
      abstract boolean isRequestByProxy();
}

复制代码

3.3.3 Uncertain steps, de-differentiated implementation for subclasses

Because it is uncertain whether to go through the proxy process , it is implemented for subclasses.

Merchant A's request implementation:

CompanyAServiceImpl extends AbstractMerchantService{
    Resp hander(req){
      return handlerTempPlate(req);
    }
    //走http代理的
    boolean isRequestByProxy(){
       return true;
    }

复制代码

Merchant B's request implementation:


CompanyBServiceImpl extends AbstractMerchantService{
    Resp hander(req){
      return handlerTempPlate(req);
    }
    //公司B是不走代理的
    boolean isRequestByProxy(){
       return false;
    }
复制代码

4. Observer Pattern

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 to the user, or send an email, etc., so there are often the following codes:

void register(User user){
  insertRegisterUser(user);
  sendIMMessage();
  sendEmail();
}
复制代码

What's wrong with this code? If there is another demand for the product: the user who has successfully registered now will send another SMS notification to the user. So you have to change the code of the register method again. . . Is this a violation of the open-closed principle ?

void register(User user){
  insertRegisterUser(user);
  sendIMMessage();
  sendMobileMessage();
  sendEmail();
}
复制代码

Also, if the interface for sending SMS messages fails , will it affect user registration again? ! At this time, is it necessary to add an asynchronous method to the notification message . . .

Actually, we can optimize using the observer pattern.

4.2 Observer Pattern Definition

The observer pattern defines a one-to-many dependency between objects. When the state of an object changes, all the objects that depend on it are notified and updated to complete the business.

The observer mode belongs to the behavior mode. When the state of an object (observed) changes, all dependent objects (observer objects) will be notified and broadcast notification. Its main members are the observer and the observed .

  • Observer (Observerable): The target object will notify all observers when the state changes.
  • Observer (observer): Receives the state change notification of the observed object and executes the predefined business.

Usage scenario: When something is done, the scenario is notified asynchronously. For example, log in successfully, send an IM message and so on.

4.3 Using the Observer Pattern

If the observer pattern is implemented, it is relatively simple.

  • An Observer's class Observerable;
  • multiple observers Observer;
  • Differentiated Implementation of Observers
  • Classic Observer Pattern Encapsulation: EventBus in Action

4.3.1 An Observed Class Observerable and Multiple Observers Observer

public class Observerable {
   
   private List<Observer> observers 
      = new ArrayList<Observer>();
   private int state;
 
   public int getState() {
      return state;
   }
 
   public void setState(int state) {
      notifyAllObservers();
   }
 
   //添加观察者
   public void addServer(Observer observer){
      observers.add(observer);      
   }
   
   //移除观察者
   public void removeServer(Observer observer){
      observers.remove(observer);      
   }
   //通知
   public void notifyAllObservers(int state){
      if(state!=1){
          System.out.println(“不是通知的状态”);
         return ;
      }
   
      for (Observer observer : observers) {
         observer.doEvent();
      }
   }  
}
复制代码

4.3.2 Differentiated Implementation of Observers

 //观察者
 interface Observer {  
    void doEvent();  
}  
//Im消息
IMMessageObserver implements Observer{
    void doEvent(){
       System.out.println("发送IM消息");
    }
}

//手机短信
MobileNoObserver implements Observer{
    void doEvent(){
       System.out.println("发送短信消息");
    }
}
//EmailNo
EmailObserver implements Observer{
    void doEvent(){
       System.out.println("发送email消息");
    }
}

复制代码

4.3.3 EventBus combat

It's still a little troublesome to create a set of code for the observer pattern by yourself. In fact, Guava EventBus is packaged. It provides a set of annotation-based event buses, and the api can be used flexibly.

Let's take a look at the actual combat code of EventBus. First, we can declare an EventBusCenter class, which is similar to the above-mentioned role Observerable.

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);
    }
}
复制代码

Then declare the observer EventListener

public class EventListener {

    @Subscribe //加了订阅,这里标记这个方法是事件处理方法  
    public void handle(NotifyEvent notifyEvent) {
        System.out.println("发送IM消息" + notifyEvent.getImNo());
        System.out.println("发送短信消息" + notifyEvent.getMobileNo());
        System.out.println("发送Email消息" + 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;
    }
 }

复制代码

Test with demo:

public class EventBusDemoTest {

    public static void main(String[] args) {

        EventListener eventListener = new EventListener();
        EventBusCenter.register(eventListener);
        EventBusCenter.post(new NotifyEvent("13372817283", "[email protected]", "666"));
        }
}

复制代码

operation result:

发送IM消息666
发送短信消息13372817283
发送Email消息[email protected]
复制代码

5. Factory Pattern

5.1 Business Scenario

The factory pattern is generally used in conjunction with the strategy pattern. Used to optimize a large number of if...else... or switch...case... conditional statements.

Let's take the example of the strategy pattern in the first subsection. 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.

The example of the strategy pattern does not use the previous code, but uses the characteristics of spring to create a factory pattern, haha, you can go back to that example and take a closer look. I will move the code down, and you can taste it again. Bar:

/**
 *  @author 公众号:捡田螺的小男孩
 */
@Component
public class StrategyUseService implements ApplicationContextAware{

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

    //把所有的文件类型解析的对象,放到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));
    }
}
复制代码

5.2 Using the Factory Pattern

Defining the factory pattern is also relatively simple:

  • A factory interface that provides a method for creating different objects.
  • Its subclasses implement the factory interface to construct different objects
  • Use factory pattern

5.3.1 A Factory Interface

interface IFileResolveFactory{
   void resolve();
}
复制代码

5.3.2 Different subclasses implement the factory interface

class AFileResolve implements IFileResolveFactory{
   void resolve(){
      System.out.println("文件A类型解析");
   }
}

class BFileResolve implements IFileResolveFactory{
   void resolve(){
      System.out.println("文件B类型解析");
   }
}

class DefaultFileResolve implements IFileResolveFactory{
   void resolve(){
      System.out.println("默认文件类型解析");
   }
}
复制代码

5.3.3 Using the Factory Pattern

//构造不同的工厂对象
IFileResolveFactory fileResolveFactory;
if(fileType=“A”){
    fileResolveFactory = new AFileResolve();
}else if(fileType=“B”){
    fileResolveFactory = new BFileResolve();
 }else{
    fileResolveFactory = new DefaultFileResolve();
}

fileResolveFactory.resolve();
复制代码

Normally, for the factory pattern, you won't see the above code. The factory pattern will appear along with other design patterns such as the strategy pattern.

6. Singleton Pattern

6.1 Business Scenario

The singleton pattern ensures that there is only one instance of a class and provides a global access point to it. The connection between I/O and the database is generally implemented in the singleton mode. The Task Manager in Windows is also a typical singleton mode.

Let's see 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;
    }

}
 
复制代码

The above example is a lazy man-style singleton implementation. Instances are created only when they are needed, which is lazy. If there is, return it, if not, create a new one. You need to add the synchronized keyword, otherwise there may be linear security problems .

6.2 Classical writing of singleton pattern

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

6.2.1 Hungry Man Mode

public class EHanSingleton {

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

   public static EHanSingleton getInstance() {
       return instance;
   }
   
}
复制代码

Hungry man mode, it is more hungry and more diligent , the instance is already created when it is initialized, whether you need it later, just create a new instance first. There is no thread safety problem, but it is a waste of memory space.

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;
   }
}
复制代码

The singleton mode implemented by the double check lock combines the advantages and disadvantages of both lazy and hungry styles. 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 compared with direct locking, and saves memory space.

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;
   }
}
复制代码

The implementation of the static inner class is somewhat similar to the double-check lock. However, this method is only suitable for static domain scenarios, and the double-check lock method can be used when the instance domain needs to be initialized lazily.

6.2.4 Enumeration

public enum SingletonEnum {

    INSTANCE;
    public SingletonEnum getInstance(){
        return INSTANCE;
    }
}
复制代码

The singleton of enumeration implementation, the code is concise and clear. And it also automatically supports the serialization mechanism, absolutely preventing multiple instantiations.


Author: little boy picking up snails
Link: https://juejin.cn/post/7023536216138055716
Source: Rare Earth Nuggets
The copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

Guess you like

Origin blog.csdn.net/wdjnb/article/details/124324105