设计模式
设计模式的确切数量并没有一个统一的标准,因为不同的资料和文献可能会对设计模式的定义和分类有所不同。然而,最常见的设计模式集合是由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides这四位作者在他们的著作《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)中提出的23种模式。
这23种模式通常被分为三类:
-
创建型模式(Creational Patterns):提供了对象创建的机制,能够增加已有代码的灵活性和可重用性。
- 单例模式(Singleton)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
-
结构型模式(Structural Patterns):关注类和对象的组合,以形成更大的结构。
- 适配器模式(Adapter)
- 装饰器模式(Decorator)
- 代理模式(Proxy)
- 外观模式(Facade)
- 桥接模式(Bridge)
- 组合模式(Composite)
- 享元模式(Flyweight)
-
行为型模式(Behavioral Patterns):涉及对象之间的通信,以及在不同对象之间分配责任和算法。
- 责任链模式(Chain of Responsibility)
- 命令模式(Command)
- 解释器模式(Interpreter)
- 迭代器模式(Iterator)
- 中介者模式(Mediator)
- 备忘录模式(Memento)
- 观察者模式(Observer)
- 状态模式(State)
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 访问者模式(Visitor)
除了这23种模式之外,随着软件开发实践的发展,其他的设计模式也被提出和使用。例如,有一些设计模式专门针对特定领域,如用户界面设计模式、企业应用架构模式等。因此,设计模式的总数可能会随着时间和社区的发展而增加。不过,上述23种模式是最基础和最广泛认可的设计模式集合。
单例模式
单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。这种模式在需要全局状态或者需要频繁创建和销毁对象时非常有用,因为它可以减少资源消耗并提高性能。
应用场景
- 当你想要控制一个实例的创建,确保在整个应用程序中只使用一个实例时。
- 当一个实例需要频繁地创建和销毁,而这些操作开销很大时。
- 当一个实例需要作为其他实例的工厂时。
- 当需要一个全局访问点,但希望避免在每次调用时创建新实例时。
特点
- 唯一性:单例模式确保一个类只有一个实例存在。
- 全局访问:提供了一个全局访问点,可以方便地获取到这个唯一的实例。
- 延迟初始化:单例实例可以在真正需要时才进行初始化,这有助于提高程序启动速度和节省资源。
优缺点
-
优点:
- 资源节省:由于只有一个实例,可以减少资源消耗。
- 全局访问点:提供一个统一的访问点,方便获取实例。
- 控制实例个数:确保某个类只有一个实例,便于控制。
-
缺点:
- 可扩展性差:单例模式把控制对象实例的生成全权交给了类本身,无法进行扩展。
- 滥用风险:单例模式容易滥用,导致系统难以维护和调试。
- 线程安全问题:在多线程环境下,需要考虑线程安全问题。
python中的实现方式
1. 使用模块
Python的模块在一个Python解释器进程中只加载一次,因此模块自然就是一个单例。你可以简单地将你的类定义在一个模块中,然后通过导入该模块来访问这个类的唯一实例。
python
class Singleton:
pass
_singleton = Singleton()
def get_singleton():
return _singleton
使用时,只需从模块中获取实例:
python
# 使用模块导入实现
get_singleton = __import__("64 单例模式(导入的模块)").get_singleton
print(get_singleton())
print(get_singleton())
print(get_singleton())
2. 使用类变量和覆写 __new__
方法
你可以覆写类的 __new__
方法来控制实例的创建过程,确保只创建一个实例。
python
# 使用类变量和覆写 `__new__` 方法
class Singleton1:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
a = Singleton1()
b = Singleton1()
print(a)
print(b)
print(a == b)
3. 使用装饰器
创建一个装饰器来封装单例的逻辑,使得你可以简单地通过装饰一个类来使其成为单例。
第一次尝试实现,发现报错
python
# TypeError: 'Singleton2' object is not callable
# 装饰器必须放回的是个函数!不然Singleton2()返回是个实例,当然不可以再调用:Singleton2()()
def singleton(cls, *args, **kwargs):
_instance = {}
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
@singleton
class Singleton2:
pass
a = Singleton2()
b = Singleton2()
print(a)
print(b)
print(a == b)
执行发现会报错,原因如下:
_instance
字典被定义在了singleton
函数内部,这意味着每次调用装饰器时都会重新创建一个新的空字典。因此,即使同一个类多次实例化也无法保证单例模式。- 装饰器应该返回一个新的函数或者类,而不是直接返回实例对象。在当前写法中,当使用@singleton修饰Singleton2类时,并没有正确地返回一个可调用对象(比如工厂函数),而是直接返回了Singleton2类的实例。
修正后:
python
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Singleton2:
pass
a = Singleton2()
b = Singleton2()
print(a)
print(b)
print(a == b)
修正版本中:
- 使用了一个名为
instances
的字典来存储类及其对应的单一实例。 - 定义了一个内部包装函数
get_instance()
来检查给定类是否已经有对应的单一实例存在于字典中;如果不存在,则创建一个新实例并存储起来;如果已存在,则直接返回该实例。 - 最终返回这个内部包装函数而非直接返回类或者类的新实例。这样确保了每次通过该装饰器获取到的都是同一个单一示例。
*args, **kwargs,这两个参数有什么用?
*args
和 **kwargs
是用来接收任意数量的位置参数和关键字参数的。这两个参数在装饰器模式中非常有用,因为它们允许你创建一个通用装饰器,该装饰器可以适应任何具有不同初始化参数的类。
即使在示例代码中没有显式地传递任何值给 Singleton2
类,保留 *args
和 **kwargs
也是一个好习惯。这样做可以增加代码的灵活性和可重用性。如果将来你需要实现一个需要初始化参数的单例类,那么已经存在的单例装饰器就可以直接使用了。
例如:
python
def singleton(cls):
instances = {}
print("装饰器调用一次")
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
# def get_instance():
# if cls not in instances:
# instances[cls] = cls()
# return instances[cls]
return get_instance
@singleton
class Singleton2:
def __init__(self, data):
self.data = data
a = Singleton2("a")
b = Singleton2("b")
print(a)
print(a.data)
print(b)
print(b.data)
print(a == b)
如果我们没有在定义 get_instance()
函数时包含 *args
和 **kwargs
参数,那么上面这段代码就会抛出异常,因为我们试图传递一个未被接受的参数给构造函数。
4. 使用元类
通过定义一个元类,你可以在类的创建过程中控制实例的生成。
python
# 使用元类实现
class Meta(type):
instance = {}
def __new__(cls, name, base, dct):
return super().__new__(cls, name, base, dct)
def __call__(cls, *args, **kwargs):
if cls not in cls.instance:
cls.instance[cls] = super().__call__(*args, **kwargs)
return cls.instance[cls]
class Singleton3(metaclass=Meta):
pass
a = Singleton3()
b = Singleton3()
print(a)
print(b)
print(a == b)
5. 使用全局变量
在函数内部创建一个类的实例,并将其存储为全局变量,从而实现单例模式。
python
class _Singleton:
pass
_instance = _Singleton()
def get_singleton():
return _instance
6. 使用线程安全的单例模式
如果你的应用是多线程的,你可能需要确保单例模式在多线程环境下也是安全的。可以使用锁来确保只有一个线程可以创建实例。
python
# 使用线程安全的单例模式
from threading import Lock
class Singleton4:
instance = None
lock = Lock()
def __new__(cls, *args, **kwargs):
if not cls.instance:
with cls.lock:
if not cls.instance:
cls.instance = super().__new__(cls, *args, **kwargs)
return cls.instance
a = Singleton4()
b = Singleton4()
print(a)
print(b)
print(a == b)