单例模式详解:概念与实用技巧

目录

  • 单例模式
    • 单例模式结构
    • 单例模式适用场景
    • 单例模式优缺点
    • 练手题目
      • 题目描述
      • 输入描述
      • 输出描述
      • 输入示例
      • 输出示例
      • 提示信息
      • 题解

单例模式

单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

  • 只有一个实例的意思是,在整个应用程序中,只存在该类的一个实例对象,而不是创建多个相同类型的对象。

  • 全局访问点的意思是,为了让其他类能够获取到这个唯一实例,该类提供了一个全局访问点(通常是一个静态方法),通过这个方法就能获得实例。

单例模式结构

  • 私有的构造函数:防止外部代码直接创建类的实例

  • 私有的静态实例变量:保存该类的唯一实例

  • 公有的静态方法:通过公有的静态方法来获取类的实例

在这里插入图片描述

单例模式通用代码

单例模式的实现方式有多种,包括懒汉式、饿汉式等。

  • 饿汉式指的是在类加载时就已经完成了实例的创建,不管后面创建的实例有没有使用,先创建再说,所以叫做 “饿汉”。

  • 而懒汉式指的是只有在请求实例时才会创建,如果在首次请求时还没有创建,就创建一个新的实例,如果已经创建,就返回已有的实例,意思就是需要使用了再创建,所以称为“懒汉”。

饿汉模式

//单例类通过'getSingleton(获取实例)'方法进行定义,让客户端全局获得该对象实例
public class Singleton{

// 保存单例实例成员变量必须被声明为静态类型
private static final Singleton singleton=new Singleton();

// 单例的构造函数必须永远是私有类型,以防止使用`new`运算符直接调用构造方法。限制产生多个对象
private Singleton(){
	}
	
//通过该方法获得实例对象
public static Singleton getSingleton(){
return singleton;
	}
//类中其他方法,尽量是static
public static void doSomething(){
	}
}

懒汉模式

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {
        // 私有构造方法,防止外部实例化
    }
    // 使用了同步关键字来确保线程安全, 可能会影响性能
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

在懒汉模式的基础上,可以使用双重检查锁来提高性能。

public class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() {
        // 私有构造方法,防止外部实例化
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

有上限的多例模式,它是单例模式的一种扩展,采用有上限的多例模式,我们可以在设计时决定在内存中有多少个实例,方便系统进行扩展,修正单例可能存在的性能问题,提供系统的响应速度。

public class Singleton{
	
	private static int maxNum=3;//最多产生3个实例数量
	
	private static ArrayList<Singleton> List=new ArrayList<Singleton>();
	
	private static int count=0;//当前皇帝序列号
	
	static{
	for(int i=0;i<maxNum;i++){
		List.add(new Singleton());
		}
	}
	
	private Singleton(){
	
	}
	
	private static Singleton getInstance(){
	Random random=new Random();
	count=random.nextInt(maxNum);
	return List.get(count);
	}
	

}

单例模式适用场景

  1. 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。

    单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。

  2. 如果你需要更加严格地控制全局变量, 可以使用单例模式。

    单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。

在这里插入图片描述

识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。

单例模式优缺点

单例模式优点:

  • 你可以保证一个类只有一个实例。

  • 你获得了一个指向该实例的全局访问节点。

  • 仅在首次请求单例对象时对其进行初始化。

单例模式缺点:

  • 违反了单一职责原则。 该模式同时解决了两个问题。

  • 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。

  • 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。

  • 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。

  • 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。

练手题目

题目描述

小明去了一家大型商场,拿到了一个购物车,并开始购物。请你设计一个购物车管理器,记录商品添加到购物车的信息(商品名称和购买数量),并在购买结束后打印出商品清单。(在整个购物过程中,小明只有一个购物车实例存在)。

输入描述

输入包含若干行,每行包含两部分信息,分别是商品名称和购买数量。商品名称和购买数量之间用空格隔开。

输出描述

输出包含小明购物车中的所有商品及其购买数量。每行输出一种商品的信息,格式为 “商品名称 购买数量”。

输入示例

Apple 3 Banana 2 Orange 5

输出示例

Apple 3 Banana 2 Orange 5

提示信息

本道题目请使用单例设计模式, 使用私有静态变量来保存购物车实例。 使用私有构造函数防止外部直接实例化。

题解

1.简单单例模式实现。

import java.util.Scanner;
import java.util.ArrayList;

class ShoppingCart {
    
    private static ShoppingCart instance = new ShoppingCart();
    private static ArrayList<String> productNames = new ArrayList<>();
    private static ArrayList<Integer> productQuantities = new ArrayList<>();
    
    private ShoppingCart() {
        
    }
    
    public static ShoppingCart getInstance() {
		    
        return instance;
    }
    
    public void Add(String name, int quantity) {
        productNames.add(name);
        productQuantities.add(quantity);
        System.out.println(name + " " + quantity);
    }
}

public class Main {
    public static void main(String[] args) {
        ShoppingCart cart = ShoppingCart.getInstance();
        Scanner scanner = new Scanner(System.in);
        
        String inputLine;
        while (scanner.hasNextLine()) {
            inputLine = scanner.nextLine();
            
            if ("exit".equalsIgnoreCase(inputLine)) {
                break;
            }

            String[] parts = inputLine.split(" ");
            
            if (parts.length == 2) {
                String name = parts[0];
                int quantity;
                
                try {
                    quantity = Integer.parseInt(parts[1]);
                    cart.Add(name, quantity);
                } catch (NumberFormatException e) {
                    System.out.println("输入错误,请重新输入");
                }
            } else {
                System.out.println("输入错误,请重新输入");
            }
        }
        
        scanner.close();
    }
}

2.在懒汉模式的基础上,可以使用双重检查锁来提高性能。

import java.util.Scanner;  
import java.util.ArrayList;  

class ShoppingCart {
    // 购物车类的单例实例变量,使用volatile关键字确保线程安全
    private static volatile ShoppingCart instance;
    // 存储商品名称
    private static ArrayList<String> productNames = new ArrayList<>();
    // 存储商品数量
    private static ArrayList<Integer> productQuantities = new ArrayList<>();
    
    // 私有构造函数,防止外部直接创建ShoppingCart对象
    private ShoppingCart() {
        
    }
    
    // 获取购物车单例实例的方法,确保线程安全
    public static ShoppingCart getInstance() {
        if (instance == null) {
            synchronized (ShoppingCart.class) {
                if (instance == null) {
                    instance = new ShoppingCart();
                }
            }
        }
        return instance;
    }
    
    // 添加商品到购物车的方法
    public void Add(String name, int quantity) {
        productNames.add(name); 
        productQuantities.add(quantity); 
        System.out.println(name + " " + quantity);
    }
}

public class Main {
    public static void main(String[] args) {
        ShoppingCart cart = ShoppingCart.getInstance(); 
        Scanner scanner = new Scanner(System.in);  
        
        String inputLine;
        // 循环读取用户输入,直到用户输入"exit"
        while (scanner.hasNextLine()) {
            inputLine = scanner.nextLine();
            
            if ("exit".equalsIgnoreCase(inputLine)) {
                break;
            }
            
            // 使用空格分割输入的字符串,获取商品名称和数量
            String[] parts = inputLine.split(" ");
            
            // 确保输入格式正确,即包含两个部分:商品名称和数量
            if (parts.length == 2) {
                // 商品名称
                String name = parts[0];  
                // 商品数量
                int quantity; 
                
                try {
                    // 将第二部分转换为整数
                    quantity = Integer.parseInt(parts[1]);
                    cart.Add(name, quantity);  
                } catch (NumberFormatException e) {
                    // 如果转换失败,输出错误信息
                    System.out.println("转换失败,请重新输入");
                }
            } else {
                // 如果输入格式不正确,输出错误信息
                System.out.println("如果输入格式不正确,请重新输入");
            }
        }
        
        scanner.close();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【深入理解Java虚拟机】判断垃圾-引用计数法及其缺陷

什么是引用计数法 引用计数法用来判断对象是否存活 给对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器的值加一&#xff1b;当引用失效时&#xff0c;计数器的值就减一&#xff0c;任何时刻计数器为0的对象是不可能在被使用的。&#xff08;存…

c++类模板及应用

文章目录 为什么要有函数模板一般实现举例类模板举例 继承中类模板的使用特殊情况 友元函数模板类和静态成员类模板实践 为什么要有函数模板 项目需求: 实现多个函数用来返回两个数的最大值&#xff0c;要求能支持char类型、int类型、double 一般实现举例 类模板举例 继承中类…

2.2 ROS2话题通信

场景 话题通信是ROS中使用频率最高的一种通信模式&#xff0c;话题通信是基于发布订阅模式的&#xff0c;也即&#xff1a;一个节点发布消息&#xff0c;另一个节点订阅该消息。话题通信的应用场景也极其广泛&#xff0c;比如如下场景&#xff1a; 机器人在执行导航功能&#…

肺炎-X光-图像分类数据集

肺炎-X光-图像分类数据集 数据集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1bt6tf-jHqgufKqPmCFHbrQ?pwdaj54 提取码&#xff1a;aj54 数据集信息介绍&#xff1a; 文件夹 健康 中的图片数量: 1575 文件夹 新冠肺炎 中的图片数量: 1728 文件夹 普通肺炎 中的…

AI:开发者的超级助手,而非取代者

AI&#xff1a;开发者的超级助手&#xff0c;而非取代者 引言 在这个日新月异的科技时代&#xff0c;人工智能&#xff08;AI&#xff09;已悄然渗透到我们生活的方方面面&#xff0c;尤其是在软件开发领域&#xff0c;它正以一种前所未有的方式改变着我们的工作方式。作为一名…

Redis 中的通用命令(命令的返回值、复杂度、注意事项及操作演示)

Redis 中的通用命令(高频率操作) 文章目录 Redis 中的通用命令(高频率操作)Redis 的数据类型redis-cli 命令Keys 命令Exists 命令Expire 命令Ttl 命令Type命令 Redis 的数据类型 Redis 支持多种数据类型&#xff0c;整体来说&#xff0c;Redis 是一个键值对结构的&#xff0c;…

《数据结构与算法基础 by王卓老师》学习笔记——2.5线性表的链式表示与实现1

1.链式表示 2.链表举例 3.链式存储的相关术语 4.三个讨论题

【软件测试】之自动化测试

&#x1f3c0;&#x1f3c0;&#x1f3c0;来都来了&#xff0c;不妨点个关注&#xff01; &#x1f3a7;&#x1f3a7;&#x1f3a7;博客主页&#xff1a;欢迎各位大佬! 文章目录 什么是自动化测试Selenium介绍什么是SeleniumSelenium的特点工作原理 SeleniumJava环境搭建下载…

数学建模------Matlab数据可视化

目录 1.plot函数 &#xff08;1&#xff09;函数介绍 &#xff08;2&#xff09;参数介绍 &#xff08;3&#xff09;图形美化 &#xff08;4&#xff09;背景更改 &#xff08;5&#xff09;多组绘制 &#xff08;6&#xff09;图形叠加 &#xff08;7&#xff09;添加…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 英文单词联想(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 📎在线评测链接 https://app5938.acapp.acwing.com.cn/contest/2/problem/OD…

Flume集群部署(手把手部署图文详细版)

前景概要&#xff1a; Kafka消息订阅系统在大数据业务中有着重要运用&#xff0c;尤其在实时业务中&#xff0c;kafka是必不可少的组件之一。 Flume是大数据组件中重要的数据采集工具&#xff0c;我们常利用Flume采集各种数据源的数据供其他组件分析使用。例如在实时业务中&…

大白菜U盘启动工具

大白菜如何u盘启动进winpe装系统大白菜是一款非常实用的U盘启动盘制作工具&#xff0c;可以帮助用户快速地将U盘制作成启动盘&#xff0c;从而方便地进行系统安装、维护和修复等操作。官方网站&#xff1a; 大白菜u盘启动盘制作工具_大白菜u盘装系统_大白菜pe_大白菜官网-首页…

机器人控制系列教程之Stewart平台简介和运动学分析

Stewart平台简介及应用场景 六自由度 Stewart 并联机器人结构简图如下图所示&#xff0c;主要有一个固定平台和一个移动平台以及六个可伸缩的推杆组成&#xff0c;通常情况下&#xff0c;固定平台与底座连接&#xff0c;移动平台在空间具有六个自由度&#xff0c;通过六个推杆…

设置Docker中时区不生效的问题

项目中使用docker-compose&#xff0c;并通过以下方式设置了时区 environment:- SET_CONTAINER_TIMEZONEtrue- CONTAINER_TIMEZONEAsia/Shanghai 但是并没有正确生效&#xff0c;网上有很多博客都在推荐这个做法&#xff0c;另外一种是使用标准环境标量 -TZAsia/Shangehai …

大型网站软件系统架构演进过程

在我们的生活中,通常会使用大型网站系统,比如购物网站淘宝,京东,阿里1688;大型搜索引擎网站百度,社交类的如腾讯旗下的微信,QQ及新浪旗下的微博等,他们通常都有一下特点: 高并发、大流量&#xff1a;这些系统必须能够处理成千上万甚至数百万的并发用户请求&#xff0c;以及持续…

Entity Framework EF Migration 迁移

针对Code First来说关注的只有实体类。当需求变更时只需要添加新的实体类或者在实体类中添加、删除、修改属性即可。但是修改完成之后要如何将修改同步到数据库中&#xff1f; migration 机制就出现了 ●启用Migrations   ●通过Add-Migration添加Migration   ●Update-D…

Feign 原理流程图练习-01

目录 作业: 老师给的参考流程图 要求 解答 知识扩展 Feign基础原理 接口定义 代理对象生成 请求调用 请求发送 响应处理 容错与熔断 总结 作业: 老师给的参考流程图 pdf版本 【金山文档 | WPS云文档】 Feign https://kdocs.cn/l/ctbagIyxN348 ​ 要求 结合上面…

Kafka集群部署(手把手部署图文详细版)

1.1.1 部署zookpeer 在node02下载并解压zookeeper软件包 cd /usr/local wget https://archive.apache.org/dist/zookeeper/zookeeper-3.4.6/zookeeper-3.4.6.tar.gz 或者&#xff1a;scp cat192.168.28.100:/home/cat/zookeeper-3.4.6.tar.gz /tmp&#xff08;注意目录&#xf…

代码随想录算法训练营第70天图论9[1]

代码随想录算法训练营第70天:图论9 ‍ 拓扑排序精讲 卡码网&#xff1a;117. 软件构建(opens new window) 题目描述&#xff1a; 某个大型软件项目的构建系统拥有 N 个文件&#xff0c;文件编号从 0 到 N - 1&#xff0c;在这些文件中&#xff0c;某些文件依赖于其他文件的…