实战!工作中常用的设计模式

news2024/12/23 22:15:20

文章目录

  • 前言
  • 一、策略模式
    • 1.1、 业务场景
    • 1.2 、策略模式定义
    • 1.3、 策略模式使用
      • 1.3.1、一个接口,两个方法
      • 1.3.2、不同策略的差异化实现
      • 1.3.3、使用策略模式
  • 二、责任链模式
    • 2.1、业务场景
    • 2.2、责任链模式定义
    • 2.3、责任链模式使用
      • 2.3.1、一个接口或者抽象类
      • 2.3.2、每个对象差异化处理
      • 2.3.3、对象链连起来(初始化)&& 使用
  • 三、模板方法模式
    • 3.1、业务场景
    • 3.2、模板方法模式定义
    • 3.3、模板方法使用
      • 3.3.1、一个抽象类,定义骨架流程
      • 3.3.2、确定的共同方法步骤,放到抽象类
      • 3.3.3、不确定的步骤,给子类去差异化实现
  • 四、观察者模式
    • 4.1、业务场景
    • 4.2、观察者模式定义
    • 4.3、观察者模式使用
      • 4.3.1、一个被观察者的类Observerable 和 多个观察者Observer
      • 4.3.2、观察者的差异化实现
      • 4.3.3、EventBus实战
  • 五、工厂模式
    • 5.1、业务场景
    • 5.2、使用工厂模式
      • 5.2.1、一个工厂接口
      • 5.2.2、不同子类实现工厂接口
      • 5.2.3、使用工厂模式
  • 六、单例模式
    • 6.1、业务场景
    • 6.2、单例模式的经典写法
      • 6.2.1、饿汉模式
      • 6.2.2、双重校验锁
      • 6.2.3、静态内部类
      • 6.2.4、枚举


前言

平时我们写代码呢,多数情况都是流水线式写代码,基本就可以实现业务逻辑了。如何在写代码中找到乐趣呢,我觉得,最好的方式就是:使用设计模式优化自己的业务代码。


一、策略模式

1.1、 业务场景

假设有这样的业务场景,大数据系统把文件推送过来,根据不同类型采取不同的解析方式。多数的小伙伴就会写出以下的代码:

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

这个代码可能会存在哪些问题呢?

  • 如果分支变多,这里的代码就会变得臃肿,难以维护,可读性低
  • 如果你需要接入一种新的解析类型,那只能在原有代码上修改

说得专业一点的话,就是以上代码,违背了面向对象编程的开闭原则以及单一原则

  • 开闭原则(对于扩展是开放的,但是对于修改是封闭的):增加或者删除某个逻辑,都需要修改到原来代码
  • 单一原则(规定一个类应该只有一个发生变化的原因):修改任何类型的分支逻辑代码,都需要改动当前类的代码。

如果你的代码就是酱紫:有多个if...else等条件分支,并且每个条件分支,可以封装起来替换的,我们就可以使用策略模式来优化。

1.2 、策略模式定义

策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的的客户。策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。

1.3、 策略模式使用

策略模式怎么使用呢?

  • 一个接口或者抽象类,里面两个方法(一个方法匹配类型,一个可替换的逻辑实现方法)
  • 不同策略的差异化实现(就是说,不同策略的实现类)
  • 使用策略模式

1.3.1、一个接口,两个方法

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

1.3.2、不同策略的差异化实现

A 类型策略具体实现

@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类型解析具体逻辑
    }
}

B 类型策略具体实现

@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类型解析具体逻辑
    }
}

默认类型策略具体实现

@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、使用策略模式

如何使用呢?我们借助spring的生命周期,使用ApplicationContextAware接口,把对用的策略,初始化到map里面。然后对外提供resolveFile方法即可。

@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.1、业务场景

我们来看一个常见的业务场景,下订单。下订单接口,基本的逻辑,一般有参数非空校验、安全校验、黑名单校验、规则拦截等等。很多伙伴会使用异常来实现:

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

这段代码使用了异常来做逻辑条件判断,如果后续逻辑越来越复杂的话,会出现一些问题:如异常只能返回异常信息,不能返回更多的字段,这时候需要自定义异常类

并且,阿里开发手册规定:禁止用异常做逻辑判断

【强制】 异常不要用来做流程控制,条件控制。说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。

如何优化这段代码呢?可以考虑责任链模式

2.2、责任链模式定义

当你想要让一个以上的对象有机会能够处理某个请求的时候,就使用责任链模式

责任链模式为请求创建了一个接收者对象的链。执行链上有多个对象节点,每个对象节点都有机会(条件匹配)处理请求事务,如果某个对象节点处理完了,就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。

责任链模式实际上是一种处理请求的模式,它让多个处理器(对象节点)都有机会处理该请求,直到其中某个处理成功为止。责任链模式把多个处理器串成链,然后让请求在链上传递:

在这里插入图片描述

2.3、责任链模式使用

责任链模式怎么使用呢?

  • 一个接口或者抽象类
  • 每个对象差异化处理
  • 对象链(数组)初始化(连起来)

2.3.1、一个接口或者抽象类

这个接口或者抽象类,需要:

  • 有一个指向责任下一个对象的属性
  • 一个设置下一个对象的set方法
  • 给子类对象差异化实现的方法(如以下代码的doFilter方法)
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、每个对象差异化处理

责任链上,每个对象的差异化处理,如本小节的业务场景,就有参数校验对象、安全校验对象、黑名单校验对象、规则拦截对象

@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、对象链连起来(初始化)&& 使用

@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;
    }
}

运行结果如下:

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

三、模板方法模式

3.1、业务场景

假设我们有这么一个业务场景:内部系统不同商户,调用我们系统接口,去跟外部第三方系统交互(http方式)。走类似这么一个流程,如下:
在这里插入图片描述
一个请求都会经历这几个流程:

  • 查询商户信息
  • 对请求报文加签
  • 发送http请求出去
  • 对返回的报文验签

这里,有的商户可能是走代理出去的,有的是走直连。假设当前有A,B商户接入,不少伙伴可能这么实现,伪代码如下:


// 商户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(); 
   }
}

假设新加一个C商户接入,你需要再实现一套这样的代码。显然,这样代码就重复了,一些通用的方法,却在每一个子类都重新写了这一方法。

如何优化呢?可以使用模板方法模式

3.2、模板方法模式定义

定义一个操作中的算法的骨架流程,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。它的核心思想就是:定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现,这样不同的子类就可以定义出不同的步骤。

3.3、模板方法使用

  • 一个抽象类,定义骨架流程(抽象方法放一起)
  • 确定的共同方法步骤,放到抽象类(去除抽象方法标记)
  • 不确定的步骤,给子类去差异化实现

我们继续那以上的举例的业务流程例子,来一起用 模板方法优化一下哈:

3.3.1、一个抽象类,定义骨架流程

因为一个个请求经过的流程为一下步骤:

  • 查询商户信息
  • 对请求报文加签
  • 发送http请求出去
  • 对返回的报文验签

所以我们就可以定义一个抽象类,包含请求流程的几个方法,方法首先都定义为抽象方法哈:

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

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

3.3.2、确定的共同方法步骤,放到抽象类

abstract class AbstractMerchantService  { 

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

3.3.3、不确定的步骤,给子类去差异化实现

因为是否走代理流程是不确定的,所以给子类去实现。

商户A的请求实现:


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

商户B的请求实现:


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

四、观察者模式

4.1、业务场景

登陆注册应该是最常见的业务场景了。就拿注册来说事,我们经常会遇到类似的场景,就是用户注册成功后,我们给用户发一条消息,又或者发个邮件等等,因此经常有如下的代码:

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

这块代码会有什么问题呢?如果产品又加需求:现在注册成功的用户,再给用户发一条短信通知。于是你又得改register方法的代码了。。。这是不是违反了开闭原则啦。

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

并且,如果调发短信的接口失败了,是不是又影响到用户注册了?!这时候,是不是得加个异步方法给通知消息才好。。。

实际上,我们可以使用观察者模式优化。

4.2、观察者模式定义

观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被完成业务的更新。

观察者模式属于行为模式,一个对象(被观察者)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。它的主要成员就是观察者和被观察者

  • 被观察者(Observerable):目标对象,状态发生变化时,将通知所有的观察者。
  • 观察者(observer):接受被观察者的状态变化通知,执行预先定义的业务

使用场景: 完成某件事情后,异步通知场景。如,登陆成功,发个IM消息等等。

4.3、观察者模式使用

观察者模式实现的话,还是比较简单的。

  • 一个被观察者的类Observerable ;
  • 多个观察者Observer ;
  • 观察者的差异化实现
  • 经典观察者模式封装:EventBus实战

4.3.1、一个被观察者的类Observerable 和 多个观察者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、观察者的差异化实现

 //观察者
 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实战

自己搞一套观察者模式的代码,还是有点小麻烦。实际上,Guava EventBus就封装好了,它 提供一套基于注解的事件总线,api可以灵活的使用,爽歪歪。

我们来看下EventBus的实战代码哈,首先可以声明一个EventBusCenter类,它类似于以上被观察者那种角色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);
    }
}

然后再声明观察者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;
    }
 }

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

运行结果:

发送IM消息666
发送短信消息133xxxxxxxx
发送Email消息123@qq.com

五、工厂模式

5.1、业务场景

工厂模式一般配合策略模式一起使用。用来去优化大量的if...else...switch...case...条件语句。

我们就取第一小节中策略模式那个例子吧。根据不同的文件解析类型,创建不同的解析对象

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

其实这就是工厂模式,定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

策略模式的例子,没有使用上一段代码,而是借助spring的特性,搞了一个工厂模式,哈哈,小伙伴们可以回去那个例子细品一下,我把代码再搬下来,小伙伴们再品一下吧:

@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、使用工厂模式

定义工厂模式也是比较简单的:

  • 一个工厂接口,提供一个创建不同对象的方法。
  • 其子类实现工厂接口,构造不同对象
  • 使用工厂模式

5.2.1、一个工厂接口

interface IFileResolveFactory{
   void resolve();
}

5.2.2、不同子类实现工厂接口

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.2.3、使用工厂模式

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

fileResolveFactory.resolve();

一般情况下,对于工厂模式,你不会看到以上的代码。工厂模式会跟配合其他设计模式如策略模式一起出现的。

六、单例模式

6.1、业务场景

单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。I/O与数据库的连接,一般就用单例模式实现de的。Windows里面的Task Manager(任务管理器)也是很典型的单例模式。

来看一个单例模式的例子

public class LanHanSingleton {

    private static LanHanSingleton instance;

    private LanHanSingleton(){

    }

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

}
 

以上的例子,就是懒汉式的单例实现。实例在需要用到的时候,才去创建,就比较懒。如果有则返回,没有则新建,需要加下 synchronized关键字,要不然可能存在线性安全问题

6.2、单例模式的经典写法

其实单例模式还有有好几种实现方式,如饿汉模式,双重校验锁,静态内部类,枚举等实现方式。

6.2.1、饿汉模式

public class EHanSingleton {

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

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

饿汉模式,它比较饥饿、比较勤奋,实例在初始化的时候就已经建好了,不管你后面有没有用到,都先新建好实例再说。这个就没有线程安全的问题,但是呢,浪费内存空间呀。

6.2.2、双重校验锁

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

双重校验锁实现的单例模式,综合了懒汉式和饿汉式两者的优缺点。以上代码例子中,在synchronized关键字内外都加了一层 if条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。

6.2.3、静态内部类

public class InnerClassSingleton {

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

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

静态内部类的实现方式,效果有点类似双重校验锁。但这种方式只适用于静态域场景,双重校验锁方式可在实例域需要延迟初始化时使用。

6.2.4、枚举

public enum SingletonEnum {

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

枚举实现的单例,代码简洁清晰。并且它还自动支持序列化机制,绝对防止多次实例化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1181709.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

11.7加减计数器,可置位~,数字钟分秒,串转并,串累加转并,24位串并128,流水乘法器,一些乘法器

信号发生器 方波&#xff0c;就是一段时间内都输出相同的信号 锯齿波就是递增 三角波就是先增后减 加减计数器 当mode为1则加&#xff0c;Mode为0则减&#xff1b;只要为0就输出zero 这样会出问题&#xff0c;因为要求是十进制&#xff0c;但是这里并没有考虑到9之后怎么办&a…

openvino学习(一)ubuntu20.04安装openvino2022

安装openvino2022要求 操作系统 Ubuntu 18.04 长期支持 (LTS)&#xff0c;64 位 Ubuntu 20.04 长期支持 (LTS)&#xff0c;64 位 软件 CMake 3.13 或更高版本&#xff0c;64 位 GCC 7.5.0&#xff08;适用于 Ubuntu 18.04&#xff09;或 GCC 9.3.0&#xff08;适用于 Ubunt…

工具介绍——第三方软件远程连接(工具:Rustdesk)

文章目录 前言一、使用工具二、开始演示1、拿下目标主机权限后上传文件2、运行目标主机上的rustdesk-1.1.9.exe文件3、目标主机上whoami查看现在的用户4、查找目标主机上连接的文件&#xff0c;并添加连接密码5、目标主机重启rustdesk的应用程序6、本地连接主机 前言 这里主要…

“第六十三天”

这两天怎么做的这么别扭&#xff0c;为什么我的vs 的strlen函数包括终止字符了&#xff1b; 哦哦&#xff0c;明白了&#xff0c;fgets函数读取在未达到指定字长&#xff0c;或者遇见空白符之前&#xff0c;会读取前面的所有字符&#xff0c;所以会读取换行符&#xff0c;而get…

康耐视深度学习ViDi-View菜单介绍

Accept View承认当前图片标注的有效性 Clear Marking 清除当前图片的标注特征 Clear Marking & Labels清除当前图片的标注特征和标签 Process处理当前图片 Edit ROI编辑检测的区域 Edit Regions编辑(标注)特征区域 Edit Mask 编辑遮挡(屏蔽)区域 Apply Mask To Tool将遮挡(…

杨辉三角(Java实现)

public class Demo09 {public static void main(String[] args) {//第一位和最后一位都为1//除了每行的第一列之外,其余的数为a[i][j]aa[i-1][j]a[i-1][j-1];int[][] arrays new int[10][10];for(int i 0;i<10;i){if(i0){ //第一行直接赋值为0arrays[i][0]1;continue;//重…

深入理解强化学习——多臂赌博机:基于置信度上界的动作选择

分类目录&#xff1a;《深入理解强化学习》总目录 因为对动作—价值的估计总会存在不确定性&#xff0c;所以试探是必须的。贪心动作虽然在当前时刻看起来最好&#xff0c;但实际上其他一些动作可能从长远看更好。 ϵ − \epsilon- ϵ−贪心算法会尝试选择非贪心的动作&#xf…

ZipInputStream解压报错java.lang.IllegalArgumentException: MALFORMED

背景 使用jdk自带的zip工具ZipInputStream&#xff0c;去读取使用winrar在中文windows制作出来的zip文件报错。 Exception in thread "main" java.lang.IllegalArgumentException: MALFORMEDat java.util.zip.ZipCoder.toString(ZipCoder.java:58)at java.util.zip…

Qwt QwtWheel绘制滚动轮

1.简介 QwtWheel 是一个用于实现滚动轮控件的类库。它基于 Qt 框架&#xff0c;并提供了一些方便的功能来处理滚动轮的事件和绘图。 QwtWheel 类继承自 QWidget类&#xff0c;用于定义滚动轮控件的通用行为。QwtWheel 添加了特定于滚动轮的功能。 QwtWheel 可以用于创建具有滚…

Spring Cloud之多级缓存

目录 传统缓存 多级缓存 JVM进程缓存 Caffeine 缓存驱逐策略 实现进程缓存 常用Lua语法 数据类型 变量声明 循环使用 定义函数 条件控制 安装OpenResty 实现Nginx业务逻辑编写 请求参数解析 实现lua访问tomcat JSON的序列化和反序列化 Tomcat的集群负载均衡 …

云表平台突破传统,企业级低代码让软件开发速度提升

随着数字化进程的加速推进&#xff0c;软件开发效率和成本的要求也在日益提高。在这个背景下&#xff0c;低代码技术的出现为企业软件开发提供了新的解决方案。低代码开发平台以其简单易用、高效灵活的特点&#xff0c;已经成为各行各业企业进行应用开发的首选工具。 企业中低代…

生成无损压缩png和有损压缩png的做法

作者:朱金灿 来源:clever101的专栏 为什么大多数人学不会人工智能编程?>>> png是一种常用的图像格式。png一般为无损压缩,但是可以是有损压缩的。 下图都是100x100的png图像,一个是无损压缩,一个是有损压缩。 看着效果基本一样,但是它们的大小相差很大,无损…

个体诊所管理系统电子处方软件,个体诊所人员服务软件,佳易王电子处方开单系统

个体诊所管理系统电子处方软件&#xff0c;个体诊所人员服务软件&#xff0c;佳易王电子处方开单系统 软件功能&#xff1a; 1、常用配方模板&#xff1a;可以自由添加配方分类&#xff0c;预先设置药品配方。 2、正常开药&#xff1a;可以灵活选择药品&#xff0c;用法用量&…

Qt 4.8.6 的下载与安装

Qt 4.8.6 的下载与安装 Qt 4.8.6 的下载与安装下载并解压 MinGW 4.8.2Qt4.8.6 库的安装Qt Creator 3.3.0 的安装配置 Qt Creator测试 官方博客&#xff1a;https://www.yafeilinux.com/ Qt开源社区&#xff1a;https://www.qter.org/ Qt 4.8.6 的下载与安装 学习《Qt Creato…

链表题(1)

链表题 今天给大家带来道链表题的练习 链表的中间节点 先给大家奉上链接&#xff1a; https://leetcode.cn/problems/middle-of-the-linked-list/description/ 题目描述; 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0…

day4作业

设计一个Per类&#xff0c;类中包含私有成员:姓名、年龄、指针成员身高、体重&#xff0c;再设计一个Stu类&#xff0c;类中包含私有成员:成绩、Per类对象p1&#xff0c;设计这两个类的构造函数、析构函数和拷贝构造函数、拷贝赋值函数。 #include <iostream>using name…

一句话说明:企业架构框架鼻祖Zachman

问&#xff1a;禁止废话&#xff0c;一句话表达&#xff0c;Zachman是什么&#xff1f;包含哪些内容&#xff1f; 韩老师正经回答&#xff1a;Zachman是企业架构框架鼻祖&#xff0c;包含6行6列的矩阵式架构内容。6列是5W1H&#xff08;What、How、Where、Who、When、Why&…

技术分享 | app自动化测试(Android)--App 控件定位

客户端的页面通过 XML 来实现 UI 的布局&#xff0c;页面的 UI 布局作为一个树形结构&#xff0c;而树叶被定义为节点。这里的节点也就对应了要定位的元素&#xff0c;节点的上级节点&#xff0c;定义了元素的布局结构。在 XML 布局中可以使用 XPath 进行节点的定位。 App的布…

物联网AI MicroPython学习之语法 uhashlib哈希算法

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; uhashlib 介绍 实现二进制数据散列算法&#xff0c;支持sha256&#xff0c;sha1&#xff0c;MD5。 接口介绍 sha256 - 创建一个SHA256哈希对象 参数原型&#xff1a;hash_obj uhashlib.sha256([bytes]) …

CMD命令行中如何切换路径。

问题描述&#xff1a; windows命令行中我记得切换命令是cd E:命令就行了呀。但是现在好像不行了。 问题解决&#xff1a;现在windows命令行中切换命令需要在cd E:之间增加一个/D。注意是/D&#xff0c;不是\D。 具体命令如下&#xff1a;CD /D E: 结果如下如所示&#xff1a…