设计模式:单例模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

news2025/1/16 15:55:39

大家好!本节主要介绍设计模式中的单例模式。

简介:

单例模式,它是一种常用的软件设计模式,它属于创建类型。单例模式的主要目的是确保一个类仅有一个实例,并提供一个全局访问点。
在单例模式中,一个类只有一个实例,而且自行实例化并向整个系统提供。通常,单例模式的实现会使用一个私有静态变量来保存类的唯一实例,并提供一个公共静态方法来获取该实例。这个方法通常是懒加载的,即在第一次调用时才创建实例。

单例模式的使用场景:
1、配置文件的读取和存储:在项目中,通常需要读取和存储配置信息。使用单例模式可以避免频繁地创建和销毁配置对象,节省开销。
2、线程池的设计:在多线程应用中,线程池的设计可以使用单例模式。这样可以方便地对池中的线程进行控制。
3、日志应用:在应用程序中,日志是一个重要的部分。使用单例模式可以避免重复创建日志对象,提高性能。
4、数据库连接池:在数据库系统中,连接池的设计通常采用单例模式。这是因为数据库连接是一种珍贵的资源,重复创建和销毁连接会带来很大的性能开销。
5、网站的计数器:网站计数器是一个需要单例的场景。如果每个请求都创建一个计数器对象,会导致系统资源的浪费。使用单例模式可以节省资源。

需要注意的是,单例模式虽然有用,但也要谨慎使用,过度使用会导致代码的耦合度过高,不利于维护和扩展。

单例模式的创建步骤:
1、将构造函数的访问权限设置成private或者protected,从而不允许类的外部创建对象,保证对象数目只有一个。
2、用一个静态成员指针指向创建的对象。
3、提供了一个静态成员函数,用来获取这个对象的首地址。

单例模式的优点,主要包括:
1、保证所有对象都访问唯一实例。单例模式确保了一个类只有一个实例,避免了系统中出现多个相同实例的情况,提高了系统的可靠性。
2、减少内存开支和系统的性能开销。由于单例模式只创建一个对象实例,因此可以节省系统资源和提高系统性能。对于需要频繁创建和销毁的对象,单例模式可以提高系统的性能。
2、提供对唯一实例的受控访问。单例模式封装了它的唯一实例,可以严格控制客户怎样以及何时访问它,使得类的实例化过程可控。
4、可以扩展到多例模式。基于单例模式,可以通过单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。

单例模式的缺点,主要包括:
1、系统开销。虽然单例模式可以节省内存和系统性能,但是每次引用这个类的时候都要进行实例是否存在的检查,这会增加系统的开销。
2、开发混淆。当使用一个单例模式的对象的时候,特别是定义在类库中的,开发人员必须记住不能使用new关键字来实例化对象。如果开发者看不到在类库中的源代码,他们可能会对此感到惊讶。
3、对象生命周期。单例模式没有提出对象的销毁,这可能导致内存泄漏。在提供内存管理的开发语言中,只有单例模式对象自己才能将对象实例销毁,因为只有它拥有对实例的引用。在各种开发语言中,如C++,其它类可以销毁对象实例,但这将导致单例类内部的指针指向不明。
4、扩展性不佳。单例模式是基于一种先实例化、再使用的模式,对于需要动态扩展的场景并不十分适用。
5、无法被继承。单例模式的单例对象无法被子类继承,也就无法使用子类来替换父类的实例,这在一定程度上限制了代码的灵活性和可重用性。

总之,虽然单例模式可以带来一些好处,但也不能滥用单例模式,需要考虑系统的实际需求和上下文环境,根据具体情况灵活选择是否使用单例模式。


示例:


一、C#单例模式

在C#中,我们可以使用静态构造函数和静态成员变量来实现单例模式。

public class Singleton  
{  
    // 静态构造函数在类被任何静态成员使用之前自动调用  
    static Singleton()  
    {  
        // 可以在这里进行一些初始化操作  
    }  
  
    // 私有静态实例变量,以便在类外部无法访问到它  
    private static readonly Singleton instance = new Singleton();  
  
    // 私有构造函数,以便在类外部无法访问到它  
    private Singleton()  
    {  
    }  
  
    // 公共静态方法,用于访问该实例  
    public static Singleton Instance  
    {  
        get { return instance; }  
    }  
}

但,这个方法存在多线程并发问题。为了解决多线程并发问题,可以使用线程安全来保证单例模式的实现。以下是一种线程安全的单例模式实现方法:

public sealed class Singleton  
{  
    private static readonly Lazy<Singleton> _instance = new Lazy<Singleton>(() => new Singleton());  
  
    private Singleton()  
    {  
        // Private constructor to prevent instantiation from outside the class.  
    }  
  
    public static Singleton Instance  
    {  
        get { return _instance.Value; }  
    }  
}

上述代码中,使用了Lazy<T>类型来延迟初始化单例实例,直到第一次访问Instance属性时才创建实例。由于Lazy<T>是线程安全的,因此可以在多线程环境下正确地实现单例模式。

//要使用这个Singleton实例,你可以像这样访问:
Singleton singleton = Singleton.Instance;

注意,在实现单例模式时,应该避免在构造函数中进行复杂的操作或依赖其他尚未初始化的依赖项,以避免潜在的问题。

除了上述实现方法,还可以使用Mutex、Monitor或volatile关键字等其他方式来实现线程安全的单例模式。但使用Lazy<T>是较为简洁和高效的一种实现方式。


二、java单例模式

在Java中实现单例模式有多种方法,下面给出两种常用的方法:

1、饿汉式:
这种方法是在类加载的时候就已经创建了实例,因此也称为“饿汉式”单例模式。它适用于在类加载时就需要使用到该实例的场景。

public class Singleton {  
    private static Singleton instance = new Singleton();  
  
    private Singleton() {}  
  
    public static Singleton getInstance() {  
        return instance;  
    }  
}

2、懒汉式:
这种方法是在需要使用实例的时候才会创建,因此也称为“懒汉式”单例模式。它适用于在类加载时不需要使用到该实例,而在运行时才需要的场景。

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

在上面的代码中,getInstance()方法使用了synchronized关键字来实现线程同步,以避免在多线程环境下创建多个实例。但是这种方法会造成性能上的开销,因此也可以使用双重检查锁定(Double-Checked Locking)机制来提高性能:

3、双重检查锁定

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

在上面的代码中,使用volatile关键字来保证,在instance变量被初始化为非null值之前,所有线程都看到该变量的值被初始化为null,这是JMM(Java Memory Model)的Memory OrderEffects中volatile建立happens-before关系的一个应用。这样就可以避免在第一次创建实例时进行同步,从而提高性能。


三、javascript单例模式

在JavaScript中,以下是一个基本的JavaScript单例模式的例子:

class Singleton {  
  constructor() {  
    if (typeof Singleton._instance !== 'undefined') {  
      throw new Error('Singleton class already has a instance!');  
    }  
  
    this._privateProperty = 'Hello, this is a private property!';  
    Singleton._instance = this;  
  }  
  
  get instance() {  
    return Singleton._instance;  
  }  
  
  publicMethod() {  
    console.log('Hello from a public method!');  
  }  
}  
  
// 使用单例  
const instance1 = Singleton.instance;  
const instance2 = new Singleton(); // 实际上创建了一个新的实例,但是...  
const instance3 = Singleton.instance; // 仍然是同一个实例  
  
console.log(instance1 === instance3); // 输出:true  
instance1.publicMethod(); // 输出:Hello from a public method!

在这个例子中,通过创建一个类,并在其构造函数中进行检查,我们确保了只有一个实例被创建。我们还定义了一个instance属性,它返回该类的唯一实例,以及一个公共方法publicMethod。

然而,这个实现方法有一个小问题:如果我们尝试通过new Singleton()来创建新的实例,虽然实际上并没有创建新的实例,但是Singleton._instance的值却被改变了。这可能导致一些意想不到的副作用。

有一种更优雅的实现单例模式的方式,那就是使用“自执行函数”:

let Singleton = (function () {  
  let instance;  
  
  function createInstance() {  
    let object = new Object("I am the instance");  
    return object;  
  }  
  
  return {  
    getInstance: function () {  
      if (!instance) {  
        instance = createInstance();  
      }  
      return instance;  
    }  
  };  
})();  
  
// 使用单例  
const instance1 = Singleton.getInstance();  
const instance2 = Singleton.getInstance();  
  
console.log(instance1 === instance2); // 输出:true

在这个版本中,我们通过创建一个自执行函数来定义了一个对象,该对象只有一个属性getInstance,该属性是一个返回实例的函数。只有当实例不存在时,getInstance才会创建新实例。因此,每次调用Singleton.getInstance()时,都会返回同一个实例。这种方式实现单例模式更为优雅,也更安全。
 

四、C++单例模式

在C++中实现单例模式有多种方式,但最常见的方式是通过在类中定义一个静态成员变量和一个静态成员函数。以下是一种常见的实现方式:

class Singleton {  
public:  
    static Singleton& getInstance() {  
        static Singleton instance;  
        return instance;  
    }  
  
    // 防止拷贝构造函数和赋值运算符被调用,从而保证单例的唯一性  
    Singleton(Singleton const&) = delete;  
    void operator=(Singleton const&) = delete;  
  
private:  
    Singleton() {}  
};

这种方式的好处是简单且线程安全。在C++11及以上版本中,静态局部变量的初始化是线程安全的。因此,即使多个线程同时调用getInstance(),也只会创建一个实例。

这个实现方式还通过将构造函数私有化,防止其他地方创建新的实例。同时,将拷贝构造函数和赋值运算符设为删除状态,防止已有的实例被复制或赋值,从而保证了单例的唯一性。

如果你使用的是C++11及以上版本,可以进一步简化代码,使用std::call_once和std::promise来实现线程安全的单例模式:

#include <once_flag.h>  
#include <future_utils.h>  
  
class Singleton {  
public:  
    static std::future<Singleton&> getInstance() {  
        std::call_once(initFlag, &Singleton::init, nullptr);  
        return future<Singleton&>(futureFlag.get_promise().get_future());  
    }  
  
private:  
    static void init() {  
        singletonInstance = Singleton();  
    }  
  
    Singleton() {}  
    Singleton(const Singleton&) = delete;  
    void operator=(const Singleton&) = delete;  
  
    static std::once_flag initFlag;  
    static Singleton& singletonInstance;  
    static std::promise<Singleton&> futureFlag;  
};

这个实现方式使用了C++11中的std::call_once和std::promise,保证了在多线程环境中也只会初始化一次实例。需要注意的是,这种方式需要包含头文件<once_flag.h>和<future_utils.h>,这两个头文件在C++17及以上版本中是标准库的一部分。


五、python单例模式

在 Python 中实现单例模式有多种方式,下面介绍其中两种常见的方法。
1、使用模块变量
我们可以将单例实例作为模块变量,在模块被导入时初始化,并通过一个静态方法来获取实例。

# singleton.py  
class Singleton:  
    _instance = None  
  
    @staticmethod  
    def getInstance():  
        if Singleton._instance is None:  
            Singleton._instance = Singleton()  
        return Singleton._instance

使用时,我们可以从该模块中导入Singleton类,并调用getInstance()方法获取单例实例。

from singleton import Singleton  
  
# 获取单例实例  
s1 = Singleton.getInstance()  
s2 = Singleton.getInstance()  
print(s1 is s2)  # True

2、使用元类

元类是创建类的类,可以通过定义一个元类来控制类的创建过程,确保只能创建一个实例。在 Python 中,所有类都是type类的实例,我们可以通过重写type类的__call__方法来实现单例模式。    

# singleton.py  
class SingletonType(type):  
    _instances = {}  
  
    def __call__(cls, *args, **kwargs):  
        if cls not in cls._instances:  
            cls._instances[cls] = super().__call__(*args, **kwargs)  
        return cls._instances[cls]  
  
class Singleton:  
    __metaclass__ = SingletonType

使用时,我们可以直接创建Singleton类的实例,每次创建实例时都会返回同一个对象。

from singleton import Singleton  
  
# 获取单例实例  
s1 = Singleton()  
s2 = Singleton()  
print(s1 is s2)  # True	

总的来说,使用元类的方式更加简洁和直观,但使用模块变量方式更加常见,因为它不需要重写类创建过程,同时也能保证线程安全。


六、go单例模式

在Go语言中实现单例模式可以使用以下方式之一:
1、懒汉模式:
这是最基本的单例模式实现方式,但它在多线程环境下是不安全的。

type Singleton struct {  
    // 单例对象的私有字段  
}  
  
var instance *Singleton  
  
func GetInstance() *Singleton {  
    if instance == nil {  
        instance = &Singleton{} // 创建单例对象  
    }  
    return instance  
}

在多线程环境下,如果两个线程同时调用GetInstance函数,可能会创建两个实例。

2、饿汉模式:
这种模式在程序启动时就创建了单例对象,所以称为"饿汉模式"。

type Singleton struct {  
    // 单例对象的私有字段  
}  
  
var instance *Singleton = &Singleton{} // 创建单例对象  
  
func GetInstance() *Singleton {  
    return instance  
}

这种模式下,单例对象在程序启动时就被创建,所以不会存在创建多个实例的情况。

3、双重检查锁定(Double-Checked Locking):
这是在懒汉模式和饿汉模式的基础上进行的改进,它通过在懒汉模式下增加了同步锁来保证在多线程环境下只创建一个实例。

type Singleton struct {  
    // 单例对象的私有字段  
}  
  
var instance *Singleton  
var once sync.Once  
  
func GetInstance() *Singleton {  
    once.Do(func() {  
        instance = &Singleton{} // 创建单例对象  
    })  
    return instance  
}

在第一次调用GetInstance时,once.Do会执行传入的函数,也就是创建单例对象。之后的调用不会再执行该函数,直接返回已创建好的实例。使用sync.Once可以保证即使在多线程环境下,也只执行一次创建实例的操作。


七、PHP单例模式

在PHP中实现单例模式有两种方式:
1、使用互斥锁(Mutex):在创建单例实例之前,使用互斥锁确保只有一个线程可以进入临界区。你可以使用PHP的扩展库提供的互斥锁函数(例如,sem_get()和sem_acquire())来同步线程。只有获得互斥锁的线程才能创建单例实例,其他线程则等待直到锁被释放。

class Singleton {  
    private static $instance;  
    private static $mutex;  
  
    private function __construct() {  
        // 私有构造函数  
    }  
  
    public static function getInstance() {  
        if (self::$instance === null) {  
            // 获取互斥锁  
            if (!self::$mutex) {  
                self::$mutex = sem_get(sem_count() + 1, 1);  
                sem_acquire(self::$mutex);  
            }  
  
            // 创建单例实例  
            self::$instance = new self();  
  
            // 释放互斥锁  
            sem_release(self::$mutex);  
        }  
  
        return self::$instance;  
    }  
}

2、使用静态初始化器(Static Initializer):在PHP 7及以上版本中,你可以使用静态初始化器来确保单例实例只被创建一次。静态初始化器是一个在类加载时执行的方法,可以在该方法中创建单例实例。

class Singleton {  
    private static $instance;  
  
    private function __construct() {  
        // 私有构造函数  
    }  
  
    public static function getInstance() {  
        if (self::$instance === null) {  
            self::$instance = new self();  
        }  
  
        return self::$instance;  
    }  
  
    // 静态初始化方法  
    public static function __clone() { }  
}

静态初始化器的__clone()方法在尝试克隆单例实例时被调用,并且什么也不做,从而阻止了通过克隆来创建新的实例。同时,使用静态初始化器时,PHP会保证类只被加载一次,因此单例实例也只会被创建一次。

这些方法可以帮助你在多线程环境下处理并发访问问题,但需要注意的是,这些方法并不能完全保证线程安全。在并发情况下,还可能存在其他并发访问问题,例如延迟初始化问题。为了确保完全线程安全,你可以考虑使用其他设计模式,如工厂模式或读写锁(ReadWrite Lock)等。
 

《完结》

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

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

相关文章

python控制Windows桌面程序自动化模块uiautomation

github仓库地址&#xff1a;GitHub - yinkaisheng/Python-UIAutomation-for-Windows: (Donot use 3.7.6,3.8.1):snake:Python 3 wrapper of Microsoft UIAutomation. Support UIAutomation for MFC, WindowsForm, WPF, Modern UI(Metro UI), Qt, IE, Firefox, Chrome ... uiaut…

【OJ比赛日历】快周末了,不来一场比赛吗? #10.21-10.27 #11场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-10-21&#xff08;周六&#xff09; #2场比赛2023-10-22…

硬件成本节省60%,四川华迪基于OceanBase的健康大数据数仓建设实践

导语&#xff1a;本文为四川华迪数据计算平台使用 OceanBase 替代 Hadoop 的实践&#xff0c;验证了 OceanBase 在性能和存储成本方面的优势&#xff1a;节省了 60% 的硬件成本&#xff0c;并将运维工作大幅减少&#xff0c;从 Hadoop 海量组件中释放出来&#xff1b;一套系统处…

【Python】文件操作

一、文件的编码 思考:计算机只能识别:0和1&#xff0c;那么我们丰富的文本文件是如何被计算机识别&#xff0c;并存储在硬盘中呢? 答案:使用编码技术( 密码本)将内容翻译成0和1存入 编码技术即:翻译的规则&#xff0c;记录了如何将内容翻译成二进制&#xff0c;以及如何将二…

2022年下半年 软件设计师 上午试卷(41题—75题)

UML活动图用于建模 &#xff08;41&#xff09; 。以下活动图中&#xff0c;活动A1之后&#xff0c;可能的活动执行序列顺序是 &#xff08;42&#xff09; 。 &#xff08;41&#xff09; A. 系统在它的周边环境的语境中所提供的外部可见服务 B. 某一时刻一组对象以及它们之间…

【代码随想录】算法训练营 第七天 第三章 哈希表 Part 2

454. 四数相加 题目 思路 这道题相当于是两数相加的加强版&#xff0c;其实大体思路是一致的&#xff0c;只不过这道题里先把四个数组中的数两两相加&#xff0c;把和作为map的key值&#xff0c;把和出现的次数作为value&#xff0c;这样先遍历完前两个数组&#xff0c;后面再…

nginx平滑升级添加echo模块、localtion配置、rewrite配置

nginx平滑升级添加echo模块、location配置、rewrite配置 文章目录 nginx平滑升级添加echo模块、location配置、rewrite配置1.环境说明&#xff1a;2.nginx平滑升级原理&#xff1a;3.平滑升级nginx&#xff0c;并添加echo模块3.1.查看当前nginx版本以及老版本编译参数信息3.2.下…

【LeetCode-数组】--搜索插入位置

搜索插入位置 class Solution {public int searchInsert(int[] nums, int target) {int n nums.length;int left 0,right n-1;while(left < right){int mid (left right) / 2;if(nums[mid] target){return mid;}else if(nums[mid] > target){right mid - 1;}else…

【一:实战开发testng的介绍】

目录 1、主要内容1.1、为啥要做接口测试1.2、接口自动化测试落地过程1.3、接口测试范围1.4、手工接口常用的工具1.5、自动化框架的设计 2、testng自动化测试框架基本测试1、基本注解2、忽略测试3、依赖测试4、超时测试5、异常测试6、通过xml文件参数测试7、通过data实现数据驱动…

边写代码边学习之mlflow

1. 简介 MLflow 是一个多功能、可扩展的开源平台&#xff0c;用于管理整个机器学习生命周期的工作流程和工件。 它与许多流行的 ML 库内置集成&#xff0c;但可以与任何库、算法或部署工具一起使用。 它被设计为可扩展的&#xff0c;因此您可以编写插件来支持新的工作流程、库和…

Go学习第二章——变量与数据类型

Go变量与数据类型 1 变量1.1 变量概念1.2 变量的使用步骤1.3 变量的注意事项1.4 ""的使用 2 数据类型介绍3 整数类型3.1 有符号整数类型3.2 无符号整数类型3.3 其他整数类型3.4 整型的使用细节 4 小数类型/浮点型4.1 浮点型的分类4.2 简单使用 5 字符类型5.1 字符类型…

【LeetCode】 412. Fizz Buzz

题目链接 文章目录 Python3 【O(n) O(1)】C.emplace_back() 【C 11 之后】 Python3 【O(n) O(1)】 初始版本 class Solution:def fizzBuzz(self, n: int) -> List[str]:ans []for i in range(1, n1):if i % 5 0 and i % 3 0:ans.append("FizzBuzz")elif i % …

【三:Mock服务的使用】

目录 1、工具包2、mock的demo1、get请求2、post请求3、带cookies的请求4、带请求头的请求5、请求重定向 1、工具包 1、&#xff1a;服务包的下载 moco-runner-0.11.0-standalone.jar 下载 2、&#xff1a;运行命令java -jar ./moco-runner-0.11.0-standalone.jar http -p 888…

【Qt控件之微调框、进度条】QSpinBox、QDoubleSpinBox、QDial、QProgressBar介绍及使用

概述 QSpinBox类提供了一个微调框小部件。 QSpinBox适用于处理整数和离散的值集&#xff08;例如&#xff0c;月份名称&#xff09;&#xff1b;对于浮点数值&#xff0c;请使用QDoubleSpinBox。 QSpinBox允许用户通过点击上下按钮或按键盘上的上下箭头来增加/减少当前显示的值…

【交互式分割】——数据可视化

ritm, 交互式分割 数据可视化 数据包括一张图片 正样本点 负样本点 二分类的mask标签 如何模拟多次点击的迭代过程&#xff1f;

ubuntu18.04 RTX3060 rangnet++训练

代码链接&#xff1a; https://github.com/PRBonn/lidar-bonnetal 安装anaconda环境为 CUDA 11.0&#xff08;11.1也可以&#xff09; anaconda环境如下 numpy1.17.2 torchvision0.2.2 matplotlib2.2.3 tensorflow1.13.1 scipy0.19.1 pytorch1.7.1 vispy0.5.3 opencv_python…

【Qt控件之QListWidget】介绍及使用,利用QListWidget、QToolButton、和布局控件实现抽屉式组合控件

概述 QListWidget类提供了基于项目的列表小部件。 QListWidget是一个方便的类&#xff0c;类似于QListView提供的列表视图&#xff0c;但使用经典的基于项目的接口来添加和删除项目。QListWidget使用内部模型来管理列表中的每个QListWidgetItem。 对于更灵活的列表视图小部件…

DVWA-impossible代码审计

文章目录 DVWA靶场—impossible代码审计1.暴力破解&#xff08;Brute Force&#xff09;1.1 代码审计1.2 总结 2.命令注入&#xff08;Command Injection&#xff09;2.1 代码审计2.2 总结 3.跨站请求伪造&#xff08;CSRF&#xff09;3.1 代码审计3.2 总结 4.文件包含漏洞&…

数据挖掘原理与算法

一、什么是闭合项集? Close算法对Apriori算法的改进在什么地方? 闭合项集&#xff1a;就是指一个项集x&#xff0c;它的直接超集的支持度计数都不等于它本身的支持度计数。 改进的地方&#xff1a; 改进方向&#xff1a; 加速频繁项目集合的生成&#xff0c;减少数据库库的扫…

数字秒表VHDL实验箱精度毫秒可回看,视频/代码

名称&#xff1a;数字秒表VHDL精度毫秒可回看 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a; 数字秒表的VHDL设计&#xff0c;可以显示秒和毫秒。可以启动、停止、复位。要求可以存储6组时间&#xff0c;可以回看存储的时间 本资源内含2个工程文件&am…