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

一,什么是单例模式

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

适用场景------该程序运行过程中只能生成一个实例,以避免对同一资源产生相互冲突的请求

  • 需要一个全局唯一的对象来协调整个系统的行为,如配置管理器,统一管理系统配置。

  • 资源共享的情况,如共享的数据库连接池,使用一个数据库对象对数据库进行操作,以维护数据的一致性。

  • 控制资源的情况,如管理打印机的使用。

  • 日志记录器(Logger):通常在应用程序中只需要一个日志实例,仅使用一个日志类的对象,将多项服务的日志信息按照顺序转储到一个特定的日志文件中。

总之,单例模式的意图就是:

  • 确保类有且只有一个对象被创建。

  • 为对象提供一个访问点,以使程序可以全局访问该对象。

  • 控制共享资源的并行访问。

二,python 代码

(一)最简单的实现

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

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 中。
  • 返回这个唯一的实例。

(二)提高灵活性

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

python 复制代码
# 原始版本
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__ 方法:

python 复制代码
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扩展到多个参数。
  • 内存效率:只为独特的参数组合创建新实例。

(四)线程安全

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

python 复制代码
# 线程不安全的单例实现
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,它们都会尝试创建新实例。
  • 这可能导致多个实例被创建,违反了单例模式的核心原则。

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

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

这里就用锁来改进:

python 复制代码
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__ 方法,这里结合了参数化单例和线程安全性,非常适用于需要基于不同参数创建不同单例实例,同时又需要确保线程安全的场景。

python 复制代码
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代码

(一)简单单例模式

最直接的方法:

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. 模块化导出:单例模式在许多场景中是全局的,我们可以通过导出模块来确保全局唯一性。
javascript 复制代码
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 类导出为模块,我们可以确保它在整个应用程序中只有一个实例。

    javascript 复制代码
    // 导出
    export default Singleton;
    
    // 在其他模块中可以使用:
    import Singleton from './Singleton';
    const instance = Singleton.getInstance();

(三)接受任意参数

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

javascript 复制代码
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)是管理数据库连接的一种技术,旨在提高数据库访问的效率和性能。它通过维护一个连接池,池中包含一组已经建立的数据库连接,供应用程序重复使用,而不是每次需要数据库连接时都创建一个新的连接。

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

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

python 复制代码
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,配置管理器

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

javascript 复制代码
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 和持久化支持。

javascript 复制代码
// 全局存储所有 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,使其具有响应性:

javascript 复制代码
import { reactive } from 'vue';

// 在 defineStore 中替换 state 的初始化方式
const state = reactive(options.state ? options.state() : {});
相关推荐
MessiGo4 分钟前
Python 爬虫 (1)基础 | 基础操作
开发语言·python
小白不太白9507 分钟前
设计模式之 模板方法模式
java·设计模式·模板方法模式
肥猪猪爸27 分钟前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
Myli_ing28 分钟前
考研倒计时-配色+1
前端·javascript·考研
色空大师30 分钟前
23种设计模式
java·开发语言·设计模式
余道各努力,千里自同风30 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
闲人一枚(学习中)31 分钟前
设计模式-创建型-建造者模式
java·设计模式·建造者模式
PandaCave37 分钟前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟39 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
LZXCyrus1 小时前
【杂记】vLLM如何指定GPU单卡/多卡离线推理
人工智能·经验分享·python·深度学习·语言模型·llm·vllm