一,什么是单例模式
单例模式(Singleton Pattern) 是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。
适用场景——该程序运行过程中只能生成一个实例,以避免对同一资源产生相互冲突的请求。
- 需要一个全局唯一的对象来协调整个系统的行为,如配置管理器,统一管理系统配置。
- 资源共享的情况,如共享的数据库连接池,使用一个数据库对象对数据库进行操作,以维护数据的一致性。
- 控制资源的情况,如管理打印机的使用。
- 日志记录器(Logger):通常在应用程序中只需要一个日志实例,仅使用一个日志类的对象,将多项服务的日志信息按照顺序转储到一个特定的日志文件中。
总之,单例模式的意图就是:
- 确保类有且只有一个对象被创建。
- 为对象提供一个访问点,以使程序可以全局访问该对象。
- 控制共享资源的并行访问。
二,python 代码
(一)最简单的实现
实现单例模式的一个简单方法:
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
# 初始化代码可以放在这里
return cls._instance
def some_business_logic(self):
# 单例的业务逻辑方法
pass
# 使用示例
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # 输出: True
- 使用
__new__
方法来控制实例的创建。 - 通过
hasattr(cls, '_instance')
检查类是否已经有一个实例。 - 如果没有实例,就创建一个新的实例并保存在类属性
_instance
中。 - 返回这个唯一的实例。
(二)提高灵活性
改进一下代码:改进版本主要是为了提高代码的可读性和灵活性
# 原始版本
class Singleton(object):
_instance = None
def __new__(cls):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
# 改进版本
class SingletonImproved:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
# 初始化代码可以放在这里
return cls._instance
def __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 = args
self.kwargs = kwargs
self.initialized = True
print(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 = None
def __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 = None
def __new__(cls):
if cls._instance is None:
time.sleep(0.1) # 模拟耗时操作
cls._instance = super().__new__(cls)
return cls._instance
# 线程安全的单例实现
import threading
class 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.futures
class 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 = parameter
self.initialized = True
print(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
- 在构造函数中检查是否已经存在实例,如果不存在则创建。
- 所有的实例化都返回同一个实例。
(二)改进
虽然上面的代码实现了单例模式,但有一些改进可以让它更健壮、更灵活。以下是一些改进建议:
- 防止修改单例实例:通过 Object.freeze 冻结实例,防止外部修改实例属性或方法。
- 惰性初始化:只有在需要时才创建单例实例,而不是在类定义时立即创建。
- 模块化导出:单例模式在许多场景中是全局的,我们可以通过导出模块来确保全局唯一性。
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
解释:
- 惰性初始化:改进后的代码使用 getInstance() 静态方法来延迟实例化。这意味着实例只有在第一次调用 getInstance() 时才会被创建,从而节省了资源。
- 防止修改实例:通过 Object.freeze(Singleton.instance) 来冻结实例,防止外部代码修改单例的状态或方法,从而保证实例的一致性。
- 模块化导出(可选):通过将 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
- 惰性初始化:Singleton.getInstance(…args) 方法负责实例化类,如果实例已经存在,它将返回现有的实例;如果实例不存在,它将创建一个新实例,并将 args 参数传递给构造函数。
- 接受任意参数:构造函数 constructor(…args) 可以接受任意数量的参数,并将它们传递给 init() 方法进行初始化。
这些参数被保存在 this.params 中,便于后续访问。
四,实际应用
(一)python——数据库连接池管理器
数据库连接池(Database Connection Pool)是管理数据库连接的一种技术,旨在提高数据库访问的效率和性能。它通过维护一个连接池,池中包含一组已经建立的数据库连接,供应用程序重复使用,而不是每次需要数据库连接时都创建一个新的连接。
- 连接池初始化:应用程序启动时,连接池会创建并维护一定数量的数据库连接。
- 获取连接:当应用程序需要访问数据库时,它从连接池中请求一个空闲的连接。
- 使用连接:应用程序使用这个连接进行数据库操作。
- 释放连接:操作完成后,应用程序将连接返回到连接池,而不是关闭它。
- 重复使用:连接池中的连接可以被多个请求重复使用,避免了频繁的连接创建和销毁。
将以一个数据库连接池管理器为例,展示单例模式的一个常见应用场景。
import random
import threading
import time
class 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_name
self.connections = []
self.max_connections = 5
self.initialized = True
print(f"初始化 {db_name} 数据库的连接池")
def get_connection(self):
if not self.connections:
new_connection = self._create_connection()
print(f"为 {self.db_name} 创建了新连接")
return new_connection
return self.connections.pop()
def release_connection(self, connection):
if len(self.connections) < self.max_connections:
self.connections.append(connection)
else:
del connection
def _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() : {};
// 定义 store
const store = {
state, // 保存状态
actions: {}, // 保存动作
getters: {}, // 保存 getter
};
// 处理 actions
if (options.actions) {
for (const [key, action] of Object.entries(options.actions)) {
store.actions[key] = action.bind(store); // 绑定 store 上下文
}
}
// 处理 getters
if (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); // 输出: 2
counterStore2.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() : {});