Chapter3 Creational Design Pattern

1、单例模式的定义、用途、实现方法

 

单例模式(Singleton Pattern)是一种创建型设计模式,其核心目标是确保一个类只有一个实例,并为该实例提供全局访问点。在 Python 中,它通常用于管理全局唯一的资源(如配置、日志、数据库连接池等)。


核心概念

  1. 唯一性
    无论创建多少次对象,实际只会生成第一个实例,后续调用返回同一个实例。

  2. 全局访问
    通过类方法或模块级变量提供统一的访问入口,避免重复创建。


Python 实现方式

方法 1:使用模块(Pythonic 方式)

Python 的模块天然支持单例模式,因为模块在程序中只会被加载一次。

# singleton.py
class Singleton:
    def __init__(self):
        self.data = "I'm the only instance!"

_instance = Singleton()

def get_instance():
    return _instance

# 使用方式
from singleton import get_instance
obj = get_instance()

 

方法 2:重写 __new__ 方法

通过控制实例化过程,确保只创建一个对象。

class Singleton:
    _instance = None  # 类变量保存唯一实例
    
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

# 测试
a = Singleton()
b = Singleton()
print(a is b)  # 输出 True

 

方法 3:使用装饰器

通过装饰器包装类,控制实例的创建。

def singleton(cls):
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper

@singleton
class Logger:
    def __init__(self):
        self.log_level = "DEBUG"

# 测试
logger1 = Logger()
logger2 = Logger()
print(logger1 is logger2)  # 输出 True

 

方法 4:使用元类(Metaclass)

通过元类控制类的创建过程。

class SingletonMeta(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 Database(metaclass=SingletonMeta):
    def __init__(self):
        self.connection = "Connected"

# 测试
db1 = Database()
db2 = Database()
print(db1 is db2)  # 输出 True

 

难点理解, cls._instances[cls] 这种写法的理解?

  • cls__call__ 方法中,表示的是元类 Singleton 的类对象,但它会被用来操作当前类(DataBase)。
  • cls._instance[cls] 中的两个 cls,实际上是不同的:
    • 第一个 cls 是元类 Singleton,它操作的是元类的实例(即 DataBase
    • 第二个 cls 代表的是当前类(在调用 DataBase() 时,指的就是 DataBase 类本身),它作为字典的键,存储对应类的单例实例。

这就是这两个 clscls._instance[cls] 中的区别和作用。

 

那这里的第一个参数, cls是属于第一个还是第二个? def __call__(cls, *args, **kwargs):

在元类的 __call__ 方法中,第一个参数 cls 代表的是当前调用元类的类,而不是实例。具体来说:

  • 这个 cls 是在执行 DataBase() 时传入的,而 DataBase 使用的是 Singleton 元类。所以,cls 在这里实际上是 Singleton 元类,即元类本身,而不是 DataBase 类。

 

应用场景

  • 配置管理:全局唯一的配置对象。

  • 日志记录器:统一管理日志写入。

  • 数据库连接池:避免重复创建连接。

  • 缓存系统:共享缓存数据。


注意事项

  1. 线程安全
    多线程环境下需加锁(如使用 threading.Lock)确保原子操作。

  2. 可测试性
    单例的全局状态可能增加测试复杂度,可通过依赖注入优化。

  3. 过度使用问题
    单例会引入全局耦合,仅在必要时使用。


单例模式通过控制实例化过程,确保资源的高效利用和一致性,是 Python 中管理全局资源的常用模式。

 

2, 设计模式中,工厂模式抽象工厂模式的概念和区别,python代码分别举例说明

工厂模式和抽象工厂模式都是创建型设计模式,主要用于对象的创建。让我们分别看看它们:

  1. 工厂模式(Factory Pattern)
  • 工厂模式提供了一个单一的工厂类,根据传入的参数决定创建哪个产品类的实例
  • 一个工厂类负责创建一个产品等级结构中的产品对象

让我们看一个简单的动物工厂示例:

# 产品基类
class Animal:
    def speak(self):
        pass

# 具体产品
class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

# 工厂类
class AnimalFactory:
    def create_animal(self, animal_type):
        if animal_type == "dog":
            return Dog()
        elif animal_type == "cat":
            return Cat()
        else:
            raise ValueError("Unknown animal type")

# 使用示例
factory = AnimalFactory()
dog = factory.create_animal("dog")
print(dog.speak())  # 输出: Woof!
cat = factory.create_animal("cat")
print(cat.speak())  # 输出: Meow!

 

抽象工厂模式(Abstract Factory Pattern)

  • 抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口
  • 无需指定它们的具体类,可以创建多个产品等级结构中的产品对象

让我们用一个家具制造的例子来说明:

from abc import ABC, abstractmethod

# 抽象产品
class Chair(ABC):
    @abstractmethod
    def sit_on(self):
        pass

class Table(ABC):
    @abstractmethod
    def put_on(self):
        pass

# 具体产品 - 现代风格
class ModernChair(Chair):
    def sit_on(self):
        return "Sitting on a modern chair"

class ModernTable(Table):
    def put_on(self):
        return "Putting on a modern table"

# 具体产品 - 古典风格
class ClassicChair(Chair):
    def sit_on(self):
        return "Sitting on a classic chair"

class ClassicTable(Table):
    def put_on(self):
        return "Putting on a classic table"

# 抽象工厂
class FurnitureFactory(ABC):
    @abstractmethod
    def create_chair(self):
        pass
    
    @abstractmethod
    def create_table(self):
        pass

# 具体工厂
class ModernFurnitureFactory(FurnitureFactory):
    def create_chair(self):
        return ModernChair()
    
    def create_table(self):
        return ModernTable()

class ClassicFurnitureFactory(FurnitureFactory):
    def create_chair(self):
        return ClassicChair()
    
    def create_table(self):
        return ClassicTable()

# 客户端代码
def create_furniture_set(factory: FurnitureFactory):
    chair = factory.create_chair()
    table = factory.create_table()
    return chair, table

# 使用示例
modern_factory = ModernFurnitureFactory()
classic_factory = ClassicFurnitureFactory()

# 创建现代家具
modern_chair, modern_table = create_furniture_set(modern_factory)
print(modern_chair.sit_on())    # 输出: Sitting on a modern chair
print(modern_table.put_on())    # 输出: Putting on a modern table

# 创建古典家具
classic_chair, classic_table = create_furniture_set(classic_factory)
print(classic_chair.sit_on())   # 输出: Sitting on a classic chair
print(classic_table.put_on())   # 输出: Putting on a classic table

主要区别:

  1. 抽象层次不同
    • 工厂模式只有一个抽象层(产品的抽象)
    • 抽象工厂模式有两个抽象层(产品的抽象和工厂的抽象)
  2. 创建对象的范围不同
    • 工厂模式创建一个产品等级结构中的一个产品
    • 抽象工厂模式创建多个产品等级结构中的产品
  3. 使用场景不同
    • 工厂模式适用于产品种类相对较少的情况
    • 抽象工厂模式适用于有多个产品系列,每个系列包含多个产品的情况
  4. 扩展性不同
    • 工厂模式增加新产品需要修改工厂类
    • 抽象工厂模式增加新的产品系列只需要增加新的工厂类,不需要修改原有代码

 

3、设计模式中的单例模式, 该如何理解,举例说明并总结特点。

如下是单例模式的特点,我将用 Python 代码来演示。

class DatabaseConnection:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            print("创建新的数据库连接实例")
            cls._instance = super().__new__(cls)
            # 初始化数据库连接
            cls._instance.initialize()
        return cls._instance
    
    def initialize(self):
        self.connected = False
        self.connection_string = "mongodb://localhost:27017"
    
    def connect(self):
        if not self.connected:
            print(f"连接到数据库: {self.connection_string}")
            self.connected = True
        else:
            print("已经连接到数据库")
    
    def disconnect(self):
        if self.connected:
            print("断开数据库连接")
            self.connected = False
        else:
            print("当前没有活跃的数据库连接")

# 测试代码
def test_singleton():
    # 创建第一个实例
    db1 = DatabaseConnection()
    db1.connect()
    
    # 创建第二个实例
    db2 = DatabaseConnection()
    db2.connect()
    
    # 验证是否是同一个实例
    print("\n验证单例:")
    print(f"db1 的 id: {id(db1)}")
    print(f"db2 的 id: {id(db2)}")
    print(f"db1 和 db2 是否是同一个实例: {db1 is db2}")
    
    # 测试断开连接
    db1.disconnect()
    db2.disconnect()  # 实际上是同一个实例

if __name__ == "__main__":
    test_singleton()

返回结果:

创建新的数据库连接实例
连接到数据库: mongodb://localhost:27017
已经连接到数据库

验证单例:
db1 的 id: 2407900395952
db2 的 id: 2407900395952
db1 和 db2 是否是同一个实例: True
断开数据库连接
当前没有活跃的数据库连接

单例模式的核心特点和使用场景:

  1. 核心特点:
    • 确保一个类只有一个实例,并提供一个全局访问点
    • 实例在首次使用时才会被创建(懒加载)
    • 所有后续获取实例的请求都返回同一个实例
    • 构造函数通常是私有的,防止直接创建新实例
  2. 适用场景
    • 数据库连接管理:避免创建多个数据库连接实例
    • 配置管理器:整个应用程序共享同一份配置信息
    • 日志记录器:集中管理日志记录
    • 打印机后台处理程序:控制打印任务队列
    • 文件系统:提供统一的文件访问接口
  3. 优点:
    • 节省系统资源,避免重复创建对象
    • 保证系统中对象的唯一性
    • 提供了对唯一实例的受控访问
  4. 注意事项:
    • 在多线程环境下需要特别注意线程安全
    • 单例模式可能会使代码的测试变得较为困难
    • 过度使用单例可能导致代码耦合度过高

 

3、追问:cls._instance = super().new(cls) 该如何进行理解?

这行代码涉及到 Python 类的实例化过程,我们需要理解几个关键点:

  1. __new__ 方法的作用:
    • 这是 Python 类实例化过程中的第一步
    • 它在 __init__ 之前被调用
    • 负责创建并返回实例对象
    • 是一个静态方法,第一个参数通常命名为 cls,代表类本身
  2. super().__new__(cls) 的含义:
    • super() 获取父类(这里是 object 类)的引用
    • 调用父类的 __new__ 方法 【父类为什么有这个方法,看接下来追问
    • 传入 cls 参数,表示要为哪个类创建实例
    • 这会创建一个全新的对象实例

让我们通过一个更简单的例子来说明:

class Regular:
    def __new__(cls):
        print("1. __new__ 被调用")
        instance = super().__new__(cls)  # 创建实例
        print("2. 实例被创建")
        return instance
    
    def __init__(self):
        print("3. __init__ 被调用")
        self.value = 42

# 创建实例
obj = Regular()
print("4. 实例的值:", obj.value)

输出会是:

1. __new__ 被调用
2. 实例被创建
3. __init__ 被调用
4. 实例的值: 42

 

4、追问:为什么父类有这个 __new__ 方法,可以直接拿来调用?

Python 中的对象系统设计得很巧妙。让我来解释这个问题:

  1. 所有的 Python 类都继承自 object 类:
# 这两种写法是等价的
class MyClass:  # Python 3 中默认继承 object
    pass

class MyClass(object):  # 显式继承 object
    pass

  2. object 类实现了一些特殊方法(魔术方法),__new__ 就是其中之一:

>>> dir(object)  # 查看 object 类的所有属性和方法
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

   3. __new__ 方法是对象实例化的底层实现:

# object.__new__ 的工作原理大致是:
def __new__(cls, *args, **kwargs):
    # 1. 分配内存空间
    # 2. 创建一个原始的实例对象
    # 3. 返回这个实例
    return new_instance

让我们通过一个例子来看实例创建的完整过程:

class Example:
    def __new__(cls, *args, **kwargs):
        print("1. __new__ 被调用")
        print("2. 通过 super().__new__ 创建实例")
        instance = super().__new__(cls)
        print("3. 实例类型:", type(instance))
        return instance

    def __init__(self):
        print("4. __init__ 被调用")
        print("5. 开始初始化实例")

# 创建实例
obj = Example()
1. __new__ 被调用
2. 通过 super().__new__ 创建实例
3. 实例类型: <class '__main__.Example'>
4. __init__ 被调用
5. 开始初始化实例

这个过程说明:

  1. 当你调用 Example() 时:
    • 首先调用 __new__ 方法
    • __new__ 必须返回一个实例对象
    • 如果返回的是 Example 类的实例,则会继续调用 __init__
  2. super().__new__(cls) 的作用:
    • 调用父类(object)的 __new__ 方法
    • 创建一个原始的实例对象
    • 这个实例还没有被初始化(没有调用 __init__
  3. 为什么需要 __new__
    • 它允许我们控制实例的创建过程
    • 可以在对象创建前进行一些操作
    • 可以改变返回的实例类型
    • 是实现单例模式等特殊模式的关键
  4. __new__ vs __init__
    • __new__ 负责创建实例
    • __init__ 负责初始化实例
    • __new__ 必须返回一个实例
    • __init__ 只能返回 None

理解了这些,你就知道为什么我们可以调用 super().__new__ 了 —— 因为它是 Python 对象系统的基础部分,提供了创建对象实例的底层机制。每个类都从 object 继承了这个能力,这就是面向对象中继承的强大之处。

 

5、为什么这么懒,非得要调用父类(object)的 __new__ 方法去创建实例?自己难道不能去创建实例吗

这是一个很好的问题!让我们深入理解为什么需要调用父类的 __new__ 方法。

  1. 实例创建的底层操作:
  • 创建一个实例需要在内存中分配空间
  • 需要设置正确的类型信息
  • 需要建立正确的继承关系
  • 需要初始化所有底层的数据结构

这些都是非常底层的操作,在 Python 中是由 C 代码实现的。我们来看一个尝试自己创建实例的例子:

这会失败,因为:

  1. object() 创建的是一个基础对象
  2. 我们无法正确设置它的类型和属性
  3. 它缺少了必要的内部结构

再看一个更极端的例子:

class MyClass:
    def __new__(cls):
        # 尝试用字典模拟一个实例
        instance = {
            '__class__': cls,
            'say_hello': lambda: print("Hello!")
        }
        return instance

# 测试
obj = MyClass()
print(type(obj))  # 输出: <class 'dict'>
# obj.say_hello()  # 这会失败,因为obj是字典而不是真正的MyClass实例

这说明:

  1. 自己创建实例是非常复杂的
  2. 需要处理很多底层的细节
  3. 可能会破坏 Python 的对象模型

正确的方式是使用 super().__new__(cls)

class MyClass:
    def __new__(cls):
        # 让 object.__new__ 处理实例创建的复杂细节
        instance = super().__new__(cls)
        return instance
    
    def say_hello(self):
        print("Hello!")

# 测试
obj = MyClass()
print(type(obj))  # 输出: <class '__main__.MyClass'>
obj.say_hello()   # 正常工作

这样做的好处是:

  1. 利用了 Python 内置的实例创建机制
  2. 确保实例有正确的类型信息
  3. 保持了对象模型的完整性
  4. 支持继承和方法解析顺序(MRO)

总结:

  • 调用父类的 __new__ 是最安全和最标准的创建实例的方式
  • 它处理了所有复杂的底层细节
  • 确保实例被正确创建和初始化
  • 维护了 Python 的对象系统的完整性

这就像是在建造房子:你可以自己从头开始建造每一个部分,但使用现成的地基(object.__new__)会更安全可靠,因为它已经处理好了所有基础性的工作。

 

posted @ 2025-02-19 14:29  AlphaGeek  阅读(41)  评论(0)    收藏  举报