详解Java设计模式之单例模式(Singleton Pattern)

简介: 详解Java设计模式之单例模式(Singleton Pattern)

大家在使用Windows的时候不知道有没有注意过一个细节,在我们使用任务管理器的时候没有办法同时打开两个,也就是说,它在整个系统中只有唯一的一个实例


b3793c3c8ad8496eacb02dff57afa394.png


对于系统中的某些类来说,只有一个实例很重要 !


例如:


一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

一个班级只有一个班主任。

在 Windows中就只能打开一个任务管理器(如图上图所示)。


如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源,如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此,有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。


模式定义

单例模式是一种对象创建型模式。

  • 单例模式确保某一个类只有一个实例,而且自行实例化并向整个例,这个类称为单例类,它提供全局访问的方法。

单例模式的要点有三个:

  1. 有一个实例。
  2. 它必须自行创建这个实例。
  3. 它必须自行向整个系统提供这个实例。

模式结构

23919972255f4dbeab5cccf6544331a9.png

单例模式只包含一个 Singleton(单例角色)类,在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以使用它的唯一实例。


为了防止在外部对其实例化,将其构造函数设计为私有,在单例类内部定义了一个 Singleton类型的静态对象,作为外部共享的唯一实例。


一般情况下单例模式的代码实现如下 👇

package singleton;
/**
 * @author mengzhichao
 * @create 2021-11-28-15:47
 */
public class Singleton {
    private static Singleton instance=null; //静态私有成员变量
    //私有构造函数
    private Singleton(){
    }
    //静态共有工厂方法,返回唯一实例
    public static Singleton getInstance(){
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

为了测试单例类所创建对象的唯一性,可以编写如下客户端测试代码

package singleton;
/**
 * @author mengzhichao
 * @create 2021-11-28-15:51
 */
public class Client {
    public static void main(String[] args) {
        Singleton s1=Singleton.getInstance();
        Singleton s2=Singleton.getInstance();
        System.out.println(s1==s2);
    }
}

编译代码并运行,输出结果为:true

  • 说明两次调用getInstance()时所获取的对象是同一实例对象,且无法在外部不Singleton进行实例化,因而确保系统中只有唯一的一个Singleton对象。

55aba35184b9412d8a26d3854f57e3c1.png

在单例模式的实现过程中,需要注意以下几点:


  1. 单例类的构造函数为私有。
  2. 提供一个自身的静态私有成员变量。
  3. 提供一个公有的静态工厂方法。
  4. getInstance()方法中需要使用同步锁synchronized (Singleton.class)防止多线程同时进入造成 instance 被多次实例化。

应用场景

场景一

  • 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象。

场景二

  • 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

使用单例模式有一个必要条件: 在一个系统中要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就需要对单例模式进行改进,使之成为多例模式

不适用场景

不要使用单例模式存取全局变量,因为这违背了单例模式的用意,最好将全局变量放到对应类的静态成员中。

不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽可能及时释放连接。单例模式由于使用静态成员存储类的实例,所以可能会造成资源无法及时释放,带来一些问题。


模式优缺点

主要优点在于提供了对唯一实例的受控访问并可以节约系统资源。


  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 避免对资源的多重占用(比如写文件操作)


其主要缺点在于因为缺少抽象层而难以扩展,且单例类职责过重。


  • 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。


模式案例

一.JDK中单例模式的应用实例

  • java. lang.Runtime类。

在每一个Java应用程序里面,都有唯一的一个Runtime对象,通过这个Runtime对象,应用程序可以与其运行环境发生相互作用。在JDK中,Runtime类的源代码片段如下:

public class Runtime {
    private static Runtime currentRuntime = new Runtime();
    public static Runtime getRuntime() {
        return currentRuntime;
    }
    private Runtime() {}
    ...
}

二.Spring中的应用实例

当我们试图要从 Spring容器中获取某个类的实例时,默认情况下Spring会通过单例模式进行创建,也就是在Spring 的 bean 工厂中这个bean的实例只有一个,代码如下:

<bean id="date" class="java.util.Date" scope="singleton"/>

三.单例模式案例之身份证号码(练习)


在现实生活中,居民身份证号码具有唯一性,同一个人不允许有多个身份证号码,第一次申请身份证时将给居民分配一个身份证号码,如果之后因为遗失等原因补办时,还是使用原来的身份证号码,不会产生新的号码。

现使用单例模式模拟该场景。


创建单例类 IdentityCardNo(身份证号码类)


在单例类IdentityCardNo中除了静态工厂方法外,还可以包含一些其他业务方法﹐如本例中的setIdentityCardNo()方法和 getIdentityCardNo()方法。在工厂方法 getInstance()中,先判断对象是否存在,如果不存在则实例化一个新的对象﹐然后返回;如果存在则直接返回已经存在的对象。

package singletontest;
/**
 * @author mengzhichao
 * @create 2021-11-28-16:18
 */
public class IdentityCardNo {
    private static IdentityCardNo instance = null;
    private String no;
    private IdentityCardNo() {
    }
    public static IdentityCardNo getInstance(){
        if (instance==null){
            System.out.println("第一次办理身份证,分配新号码");
            instance=new IdentityCardNo();
            instance.setIdentityCardNo("No410111111122222222");
        }else {
            System.out.println("重复办理身份证,获取旧号码!");
        }
        return instance;
    }
    private void setIdentityCardNo(String no){
        this.no=no;
    }
    public String getIdentityCardNo(){
        return this.no;
    }
}
  1. 测试类 Client

在客户端测试代码中定义了两个IdentityCardNo类型的对象,通过调用两次静态工厂方法 getInstance()获取对象,然后判断它们是否相等﹔再通过业务方法 getIdentityCardNo()获取封装在对象中的属性号码no值,判断两次no值是否相同。

package singletontest;
/**
 * @author mengzhichao
 * @create 2021-11-28-16:26
 */
public class Client {
    public static void main(String[] args) {
        IdentityCardNo no1,no2;
        no1=IdentityCardNo.getInstance();
        no2=IdentityCardNo.getInstance();
        System.out.println("身份证号码是否一致:" + (no1==no2));
        String str1,str2;
        str1=no1.getIdentityCardNo();
        str2=no2.getIdentityCardNo();
        System.out.println("第一次号码:"+str1);
        System.out.println("第二次号码:"+str2);
        System.out.println("内容是否相同:"+str1.equalsIgnoreCase(str2));
        System.out.println("对象是否相同:"+ (str1==str2));
    }
}
  1. 运行结果

82b3a81d9dde401680207f4f4f5c9378.png

从结果可以看出,两次创建的IdentityCardNo对象内存地址相同,是同一个对象,封装在其中的号码no属性不仅值相等,其内存地址也一致,是同一个成员属性。


学习更多设计模式还请访问:https://blog.csdn.net/weixin_45692705?spm=1011.2124.3001.5343


相关文章
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
27 2
|
12天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
20天前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入理解与应用
【10月更文挑战第22天】 在软件开发中,设计模式是解决特定问题的通用解决方案。本文将通过通俗易懂的语言和实例,深入探讨PHP中单例模式的概念、实现方法及其在实际开发中的应用,帮助读者更好地理解和运用这一重要的设计模式。
15 1
|
24天前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
36 0
[Java]23种设计模式
|
8天前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。
|
26天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
25 0
|
29天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
本教程详细讲解了Kotlin中的单例模式实现,包括饿汉式、懒汉式、双重检查锁、静态内部类及枚举类等方法,适合需要深入了解Kotlin单例模式的开发者。快速学习者可参考“简洁”系列教程。
27 0
|
29天前
|
设计模式 存储 数据库连接
Python编程中的设计模式之美:单例模式的妙用与实现###
本文将深入浅出地探讨Python编程中的一种重要设计模式——单例模式。通过生动的比喻、清晰的逻辑和实用的代码示例,让读者轻松理解单例模式的核心概念、应用场景及如何在Python中高效实现。无论是初学者还是有经验的开发者,都能从中获得启发,提升对设计模式的理解和应用能力。 ###
|
Java 索引 API
java Pattern和Matcher详解
结论:Pattern与Matcher一起合作.Matcher类提供了对正则表达式的分组支持,以及对正则表达式的多次匹配支持. 单独用Pattern只能使用Pattern.matcher(String regex,CharSequence input)一种最基础最简单的匹配。 java正则表达式通过java.util.regex包下的Pattern类与Matcher类实现(
1537 0
|
10天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。