当前位置: 首页 > news >正文

设计模式(一):单例模式

一,什么是单例模式

单例模式(Singleton Pattern) 是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。

适用场景——该程序运行过程中只能生成一个实例,以避免对同一资源产生相互冲突的请求
- 需要一个全局唯一的对象来协调整个系统的行为,如配置管理器,统一管理系统配置。
- 资源共享的情况,如共享的数据库连接池,使用一个数据库对象对数据库进行操作,以维护数据的一致性。
- 控制资源的情况,如管理打印机的使用。
- 日志记录器(Logger):通常在应用程序中只需要一个日志实例,仅使用一个日志类的对象,将多项服务的日志信息按照顺序转储到一个特定的日志文件中。

总之,单例模式的意图就是:
- 确保类有且只有一个对象被创建。
- 为对象提供一个访问点,以使程序可以全局访问该对象。
- 控制共享资源的并行访问。

二,python 代码

(一)最简单的实现

实现单例模式的一个简单方法:

class Singleton:_instance = Nonedef __new__(cls):if cls._instance is None:cls._instance = super().__new__(cls)# 初始化代码可以放在这里return cls._instancedef some_business_logic(self):# 单例的业务逻辑方法pass# 使用示例
s1 = Singleton()
s2 = Singleton()print(s1 is s2)  # 输出: True
  • 使用 __new__ 方法来控制实例的创建。
  • 通过 hasattr(cls, '_instance') 检查类是否已经有一个实例。
  • 如果没有实例,就创建一个新的实例并保存在类属性 _instance 中。
  • 返回这个唯一的实例。

(二)提高灵活性

改进一下代码:改进版本主要是为了提高代码的可读性和灵活性

# 原始版本
class Singleton(object):_instance = Nonedef __new__(cls):if not hasattr(cls, '_instance'):cls._instance = super(Singleton, cls).__new__(cls)return cls._instance# 改进版本
class SingletonImproved:_instance = Nonedef __new__(cls):if cls._instance is None:cls._instance = super().__new__(cls)# 初始化代码可以放在这里return cls._instancedef __init__(self):# 初始化代码if not hasattr(self, 'initialized'):self.initialized = True# 其他初始化代码...def some_business_logic(self):# 单例的业务逻辑方法pass# 使用示例
s1 = SingletonImproved()
s2 = SingletonImproved()
print(s1 is s2)  # 输出: True
  • 使用类变量 _instance = None 来存储实例,这样更加明确和易读。
  • 使用 is None 检查而不是 hasattr,这样更加直观和高效。
  • 添加了 __init__ 方法来处理初始化逻辑。
  • __init__ 中使用一个标志来确保初始化代码只运行一次。

(三)带初始化参数

对于需要参数化的单例,可以实现一个带参数的 __new__ 方法:

class ParameterizedSingleton:_instances = {}def __new__(cls, *args, **kwargs):# 将位置参数和关键字参数组合成一个不可变的 key,用作实例的唯一标识key = (args, frozenset(kwargs.items()))# 检查是否已经存在对应的实例if key not in cls._instances:cls._instances[key] = super().__new__(cls)return cls._instances[key]def __init__(self, *args, **kwargs):# 确保 __init__ 只被调用一次if not hasattr(self, 'initialized'):self.args = argsself.kwargs = kwargsself.initialized = Trueprint(f"Initializing with args: {args} and kwargs: {kwargs}")def get_parameters(self):return self.args, self.kwargs# 创建第一个实例
instance1 = ParameterizedSingleton(10, name="test1")
print(instance1.get_parameters())  # 输出: ((10,), {'name': 'test1'})# 创建第二个实例,参数不同
instance2 = ParameterizedSingleton(20, name="test2")
print(instance2.get_parameters())  # 输出: ((20,), {'name': 'test2'})# 创建与第一个实例相同参数的实例
instance3 = ParameterizedSingleton(10, name="test1")
print(instance3.get_parameters())  # 输出: ((10,), {'name': 'test1'})# 检查单例特性
print(instance1 is instance3)  # 输出: True
print(instance1 is instance2)  # 输出: False

这个版本:

  • 参数化:可以基于不同的参数创建不同的单例实例。
  • 灵活性:可以easily扩展到多个参数。
  • 内存效率:只为独特的参数组合创建新实例。

(四)线程安全

上面的代码都不是线程安全的:

# 线程不安全的单例实现
class ThreadUnsafeSingleton:_instance = Nonedef __new__(cls):if cls._instance is None:time.sleep(0.1)  # 模拟耗时操作cls._instance = super().__new__(cls)return cls._instance

线程不安全的原因:

  • 在多线程环境中,多个线程可能同时执行到 if cls._instance is None 这一行。
  • 如果这些线程同时发现 _instance 为 None,它们都会尝试创建新实例。
  • 这可能导致多个实例被创建,违反了单例模式的核心原则。

线程不安全将导致的问题:

  • 多个实例:不同线程可能最终使用不同的实例,导致状态不一致。
  • 资源浪费:创建多个不必要的实例可能会浪费资源。
  • 不可预测的行为:依赖单例的代码可能会因为使用了不同的实例而表现出意外行为。

这里就用锁来改进:

import threading
import time# 线程不安全的单例实现
class ThreadUnsafeSingleton:_instance = Nonedef __new__(cls):if cls._instance is None:time.sleep(0.1)  # 模拟耗时操作cls._instance = super().__new__(cls)return cls._instance# 线程安全的单例实现
import threadingclass ThreadSafeSingleton:_instance = None_lock = threading.Lock()def __new__(cls):if cls._instance is None:with cls._lock:if cls._instance is None:time.sleep(0.1)  # 模拟耗时操作cls._instance = super().__new__(cls)return cls._instance# 测试函数
def test_singleton(Singleton):def create_singleton():singleton = Singleton()print(f"Instance created: {id(singleton)}")threads = [threading.Thread(target=create_singleton) for _ in range(10)]for thread in threads:thread.start()for thread in threads:thread.join()print("Testing ThreadUnsafeSingleton:")
test_singleton(ThreadUnsafeSingleton)print("\nTesting ThreadSafeSingleton:")
test_singleton(ThreadSafeSingleton)
  • 使用 threading.Lock() 来创建一个锁对象。
  • 在检查和创建实例时使用这个锁。
  • 采用双重检查锁定模式(Double-Checked Locking Pattern)来提高效率。

双重检查锁定的工作原理:

  • 第一次检查不使用锁,以避免每次获取实例都需要加锁。
  • 如果第一次检查发现实例不存在,才使用锁。
  • 加锁后再次检查,以确保在等待锁的过程中没有其他线程创建实例。

(五)带参数的线程安全版本

在线程安全的版本上实现带参数的 __new__ 方法,这里结合了参数化单例和线程安全性,非常适用于需要基于不同参数创建不同单例实例,同时又需要确保线程安全的场景。

import threading
import concurrent.futuresclass ThreadSafeParameterizedSingleton:_instances = {}_lock = threading.Lock()def __new__(cls, parameter):if parameter not in cls._instances:with cls._lock:# 双重检查锁定if parameter not in cls._instances:cls._instances[parameter] = super().__new__(cls)return cls._instances[parameter]def __init__(self, parameter):# 确保 __init__ 只被调用一次if not hasattr(self, 'initialized'):with self.__class__._lock:if not hasattr(self, 'initialized'):self.parameter = parameterself.initialized = Trueprint(f"Initializing with parameter: {parameter}")def get_parameter(self):return self.parameter# 测试函数
def test_singleton(parameter):instance = ThreadSafeParameterizedSingleton(parameter)print(f"Instance for {parameter}: {id(instance)}")return instance# 多线程测试
parameters = ["A", "B", "A", "C", "B", "A"]with concurrent.futures.ThreadPoolExecutor(max_workers=len(parameters)) as executor:futures = [executor.submit(test_singleton, param) for param in parameters]instances = [future.result() for future in concurrent.futures.as_completed(futures)]# 验证结果
for param, instance in zip(parameters, instances):print(f"Parameter: {param}, Instance ID: {id(instance)}, Value: {instance.get_parameter()}")# 检查 "A" 参数的所有实例是否相同
a_instances = [instance for instance in instances if instance.get_parameter() == "A"]
print(f"\nAll 'A' instances are the same: {len(set(a_instances)) == 1}")
  • 它定义了一个 ThreadSafeParameterizedSingleton 类,这是一个线程安全的参数化单例模式实现。
  • test_singleton 函数用于创建和测试单例实例。
  • 使用 concurrent.futures.ThreadPoolExecutor 来模拟多线程环境,测试单例的线程安全性和参数化功能。
  • 最后,代码验证了对于相同的参数(如 “A”),是否总是返回相同的实例。

使用注意事项:

  • 内存使用:如果参数种类很多,可能会占用大量内存。考虑使用弱引用或定期清理机制(用于在不需要时删除特定参数的实例)
  • 参数比较:当前实现使用参数的直接比较。对于复杂对象,可能需要自定义比较逻辑。
  • 性能:虽然已经优化,但在高并发情况下,锁操作可能成为瓶颈。

三,JavaScript代码

(一)简单单例模式

最直接的方法:

class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}this.value = Math.random(); // 模拟一些独特的实例状态Singleton.instance = this;}getValue() {return this.value;}
}// 测试单例模式
const instance1 = new Singleton();
const instance2 = new Singleton();console.log(instance1 === instance2); // 输出: true
console.log(instance1.value);
console.log(instance2.value);
console.log(instance1.getValue() === instance2.getValue()); // 输出: true
  • 在构造函数中检查是否已经存在实例,如果不存在则创建。
  • 所有的实例化都返回同一个实例。

(二)改进

虽然上面的代码实现了单例模式,但有一些改进可以让它更健壮、更灵活。以下是一些改进建议:

  1. 防止修改单例实例:通过 Object.freeze 冻结实例,防止外部修改实例属性或方法。
  2. 惰性初始化:只有在需要时才创建单例实例,而不是在类定义时立即创建。
  3. 模块化导出:单例模式在许多场景中是全局的,我们可以通过导出模块来确保全局唯一性。
class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}this.value = Math.random(); // 模拟实例状态Singleton.instance = this;// 冻结实例,防止修改Object.freeze(Singleton.instance);}getValue() {return this.value;}static getInstance() {// 如果实例不存在,创建一个新实例if (!Singleton.instance) {Singleton.instance = new Singleton();}return Singleton.instance;}
}// 测试单例模式
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();console.log(instance1 === instance2); // 输出: true
console.log(instance1.getValue() === instance2.getValue()); // 输出: true// 测试冻结的实例
instance1.value = 42;
console.log(instance1.getValue()); // 输出依然是之前的随机数,未修改为 42

解释:

  1. 惰性初始化:改进后的代码使用 getInstance() 静态方法来延迟实例化。这意味着实例只有在第一次调用 getInstance() 时才会被创建,从而节省了资源。
  2. 防止修改实例:通过 Object.freeze(Singleton.instance) 来冻结实例,防止外部代码修改单例的状态或方法,从而保证实例的一致性。
  3. 模块化导出(可选):通过将 Singleton 类导出为模块,我们可以确保它在整个应用程序中只有一个实例。
    // 导出
    export default Singleton;// 在其他模块中可以使用:
    import Singleton from './Singleton';
    const instance = Singleton.getInstance();
    

(三)接受任意参数

为了实现一个能够在惰性初始化时接受任意参数的单例模式,我们需要允许 getInstance() 方法在实例化时接收参数,并将这些参数传递给构造函数。

class Singleton {constructor(...args) {if (Singleton.instance) {return Singleton.instance;}// 保存传入的参数this.init(...args);Singleton.instance = this;// 冻结实例,防止修改Object.freeze(Singleton.instance);}// 初始化方法,保存构造函数参数init(...args) {this.params = args;}getParameters() {return this.params;}static getInstance(...args) {// 如果实例不存在,创建一个新实例,并传入参数if (!Singleton.instance) {Singleton.instance = new Singleton(...args);}return Singleton.instance;}
}// 测试单例模式
const instance1 = Singleton.getInstance(10, "Hello", { key: "value" });
console.log(instance1.getParameters()); // 输出: [10, "Hello", { key: "value" }]const instance2 = Singleton.getInstance(20, "World");
console.log(instance2.getParameters()); // 输出依然是: [10, "Hello", { key: "value" }]// 检查单例特性
console.log(instance1 === instance2); // 输出: true
  1. 惰性初始化:Singleton.getInstance(…args) 方法负责实例化类,如果实例已经存在,它将返回现有的实例;如果实例不存在,它将创建一个新实例,并将 args 参数传递给构造函数。
  2. 接受任意参数:构造函数 constructor(…args) 可以接受任意数量的参数,并将它们传递给 init() 方法进行初始化。
    这些参数被保存在 this.params 中,便于后续访问。

四,实际应用

(一)python——数据库连接池管理器

数据库连接池(Database Connection Pool)是管理数据库连接的一种技术,旨在提高数据库访问的效率和性能。它通过维护一个连接池,池中包含一组已经建立的数据库连接,供应用程序重复使用,而不是每次需要数据库连接时都创建一个新的连接。

  • 连接池初始化:应用程序启动时,连接池会创建并维护一定数量的数据库连接。
  • 获取连接:当应用程序需要访问数据库时,它从连接池中请求一个空闲的连接。
  • 使用连接:应用程序使用这个连接进行数据库操作。
  • 释放连接:操作完成后,应用程序将连接返回到连接池,而不是关闭它。
  • 重复使用:连接池中的连接可以被多个请求重复使用,避免了频繁的连接创建和销毁。

将以一个数据库连接池管理器为例,展示单例模式的一个常见应用场景。

import random
import threading
import timeclass DatabaseConnectionPool:_instances = {}_lock = threading.Lock()def __new__(cls, db_name):if db_name not in cls._instances:with cls._lock:if db_name not in cls._instances:cls._instances[db_name] = super().__new__(cls)return cls._instances[db_name]def __init__(self, db_name):if not hasattr(self, 'initialized'):with self.__class__._lock:if not hasattr(self, 'initialized'):self.db_name = db_nameself.connections = []self.max_connections = 5self.initialized = Trueprint(f"初始化 {db_name} 数据库的连接池")def get_connection(self):if not self.connections:new_connection = self._create_connection()print(f"为 {self.db_name} 创建了新连接")return new_connectionreturn self.connections.pop()def release_connection(self, connection):if len(self.connections) < self.max_connections:self.connections.append(connection)else:del connectiondef _create_connection(self):# 模拟创建数据库连接time.sleep(0.1)  # 假设连接需要一些时间return f"到 {self.db_name} 的连接"def worker(db_name):pool = DatabaseConnectionPool(db_name)connection = pool.get_connection()print(f"线程 {threading.current_thread().name} 获得了 {connection}")# 模拟使用连接time.sleep(random.uniform(0.1, 0.3))pool.release_connection(connection)# 测试
databases = ["MySQL", "PostgreSQL", "MySQL", "MongoDB", "PostgreSQL"]
threads = []for db in databases:thread = threading.Thread(target=worker, args=(db,))threads.append(thread)thread.start()for thread in threads:thread.join()# 验证结果
print("\n连接池实例验证:")
for db_name, instance in DatabaseConnectionPool._instances.items():print(f"数据库: {db_name}, 连接池ID: {id(instance)}")

1,单例模式的应用:

  • 每个数据库(由 db_name 参数标识)有一个唯一的连接池实例。
  • 这确保了对同一数据库的所有连接请求都通过同一个池来管理,避免了资源浪费。

2,参数化的必要性:

  • 不同的数据库需要不同的连接池(例如,MySQL 和 PostgreSQL)。
  • 参数化允许我们为每个数据库创建独立的连接池实例。

3,线程安全的重要性:

  • 在多线程环境中,多个线程可能同时请求数据库连接。
  • 线程安全确保了连接池的创建和管理不会因并发访问而出错。

4,连接池的实现:

  • get_connection() 方法从池中获取连接,如果池空则创建新连接。
  • release_connection() 方法将用完的连接放回池中,或在池满时销毁。
  • 使用 max_connections 限制每个池的最大连接数。

5,实际应用模拟:

  • worker 函数模拟了实际应用中如何使用连接池。
  • 创建多个线程来模拟并发访问不同数据库的场景。

6,验证:

  • 最后打印每个数据库的连接池实例 ID,确保相同数据库只创建了一个池实例。

可改进:

  • 错误处理:需要添加适当的错误处理机制,例如处理连接失败的情况。
  • 连接验证:在返回连接前,可能需要验证连接是否仍然有效。
  • 超时机制:考虑添加连接超时和池清理机制,以处理长时间不用的连接。
  • 监控:在生产环境中,应该添加监控和日志记录,以跟踪连接池的使用情况。

(二)JavaScript

1,配置管理器

在实际的应用中,单例模式常用于全局管理配置数据。配置管理器需要在整个应用中只有一个实例,以确保配置的一致性和正确性。任何时候访问配置管理器,都应返回相同的实例,这样可以避免数据冲突,并且所有组件都能共享相同的配置。

class ConfigManager {constructor(initialConfig = {}) {if (ConfigManager.instance) {return ConfigManager.instance;}// 初始化配置this.config = initialConfig;// 保存实例ConfigManager.instance = this;// 冻结实例,防止修改Object.freeze(ConfigManager.instance);}// 获取配置getConfig(key) {return this.config[key];}// 设置配置setConfig(key, value) {this.config[key] = value;}// 获取所有配置getAllConfig() {return this.config;}// 静态方法:获取单例实例static getInstance(initialConfig = {}) {if (!ConfigManager.instance) {ConfigManager.instance = new ConfigManager(initialConfig);}return ConfigManager.instance;}
}// 测试代码// 第一次获取单例实例,并设置初始配置
const config1 = ConfigManager.getInstance({ appName: "MyApp", version: "1.0" });
console.log(config1.getAllConfig()); // 输出: { appName: "MyApp", version: "1.0" }// 修改配置
config1.setConfig("appName", "YourApp");
console.log(config1.getConfig("appName")); // 输出: "YourApp"// 再次获取单例实例
const config2 = ConfigManager.getInstance();
console.log(config2.getAllConfig()); // 输出: { appName: "YourApp", version: "1.0" }// 检查单例特性
console.log(config1 === config2); // 输出: true
  • 全局配置管理:在复杂的应用程序中,可能有很多配置选项,如数据库配置、应用名称、版本信息等。单例模式可以确保全局配置的一致性,避免在不同模块中使用不一致的配置。
  • 共享资源管理:类似的,单例模式还可以用于管理全局共享资源,如数据库连接、缓存管理器等。

2,最简pinia

Pinia 是 Vue 生态中的一种状态管理库,是 Vuex 的替代方案。尽管 Pinia 并不直接被称为“单例模式”,但其工作方式具有类似的单例模式特性,特别是在全局状态管理的场景中。

Pinia 是用于管理 Vue 应用程序全局状态的状态管理库。以下是 Pinia 的工作机制:

  • 全局唯一的状态存储: 在 Vue 应用程序中,Pinia 的 store 是全局唯一的。当你在应用中创建一个 store 并导出它时,这个 store 实际上是应用中的唯一实例,无论你在应用的哪个部分使用该 store,它都指向同一个对象。
  • Vue 生态中的状态共享: 当多个组件使用相同的 store 时,它们共享同一个状态。这意味着状态变更是全局可见的,类似于单例模式中的全局共享对象。

要模拟实现类似 Pinia 的全局状态管理系统,我们可以通过 JavaScript 创建一个简单的状态管理库,该库允许你定义 store 并在全局共享状态。以下是一个简化版的实现,包括state、actions、getter 和持久化支持。

// 全局存储所有 store 的实例
const stores = {};function defineStore(id, options) {if (stores[id]) {return stores[id]; // 如果 store 已存在,直接返回}// 创建 store 的响应式状态const state = options.state ? options.state() : {};// 定义 storeconst store = {state, // 保存状态actions: {}, // 保存动作getters: {}, // 保存 getter};// 处理 actionsif (options.actions) {for (const [key, action] of Object.entries(options.actions)) {store.actions[key] = action.bind(store); // 绑定 store 上下文}}// 处理 gettersif (options.getters) {for (const [key, getter] of Object.entries(options.getters)) {Object.defineProperty(store.getters, key, {get: () => getter(store.state), // getter 通过计算属性返回值});}}// 将 store 保存在全局 stores 对象中stores[id] = store;return store;
}// 示例用法
const useCounterStore = defineStore('counter', {state: () => ({count: 0,}),actions: {increment() {this.state.count++;},reset() {this.state.count = 0;},},getters: {doubleCount(state) {return state.count * 2;},},
});// 在不同的地方使用 store
const counterStore1 = useCounterStore;
const counterStore2 = useCounterStore;counterStore1.actions.increment();
console.log(counterStore1.state.count); // 输出: 1
console.log(counterStore2.getters.doubleCount); // 输出: 2counterStore2.actions.reset();
console.log(counterStore1.state.count); // 输出: 0

如果使用 Vue 3,我们可以使用 reactive 或 ref 来替换普通的 state,使其具有响应性:

import { reactive } from 'vue';// 在 defineStore 中替换 state 的初始化方式
const state = reactive(options.state ? options.state() : {});

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 数字IC/FPGA中有符号数的处理探究
  • Python|OpenCV-基于OpenCV进行图像的复制与克隆(19)
  • 第五章 设置和其他常见活动 - 创建 IRIS 凭证集
  • 【hot100篇-python刷题记录】【买卖股票的最佳时机】
  • django之自定义序列化器用法
  • 【Java学习】反射和枚举详解
  • 微服务网关
  • 基于python的自适应svm电影评价倾向性分析设计与实现
  • 全光谱日照模拟系统汽车整车光老化测试 太阳光照射模拟器
  • 【10.2 python中的类的定义和使用】
  • SQL进阶技巧:最近有效的缺失值填充问题【last_value实现版】
  • 基于WebSocket打造的一款SSH客户端
  • iLogtail 开源两周年:感恩遇见,畅想未来
  • 《中国档案》
  • 前端调用后端,出现跨域报错怎么办
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • 【跃迁之路】【463天】刻意练习系列222(2018.05.14)
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • canvas 高仿 Apple Watch 表盘
  • ES6--对象的扩展
  • iOS小技巧之UIImagePickerController实现头像选择
  • JavaScript类型识别
  • Java小白进阶笔记(3)-初级面向对象
  • Kibana配置logstash,报表一体化
  • MD5加密原理解析及OC版原理实现
  • mongo索引构建
  • Node + FFmpeg 实现Canvas动画导出视频
  • SpiderData 2019年2月25日 DApp数据排行榜
  • uva 10370 Above Average
  • 计算机常识 - 收藏集 - 掘金
  • 技术:超级实用的电脑小技巧
  • 排序算法学习笔记
  • 前端工程化(Gulp、Webpack)-webpack
  • 应用生命周期终极 DevOps 工具包
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • (4)事件处理——(7)简单事件(Simple events)
  • (react踩过的坑)antd 如何同时获取一个select 的value和 label值
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (附源码)ssm高校社团管理系统 毕业设计 234162
  • (黑马点评)二、短信登录功能实现
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (区间dp) (经典例题) 石子合并
  • (算法)Travel Information Center
  • (一)、软硬件全开源智能手表,与手机互联,标配多表盘,功能丰富(ZSWatch-Zephyr)
  • (一)kafka实战——kafka源码编译启动
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .NET Micro Framework初体验
  • .NET 设计模式初探
  • .NET下的多线程编程—1-线程机制概述
  • .NET业务框架的构建