python开发常见的五大面向对象设计模式
一、什么是面向对象设计模式
1.定义
设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。是一种软件设计的方法,使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
2.设计模式的六大原则
为了打造高内聚、低耦合、可维护性高的面向对象系统。
2.1 开闭原则OCP
开放封闭:对扩展开发、对修改封闭。如装饰器模式。
2.2 里氏代换原则LSP
子类可以替换其父类而不影响程序的正确性。子类应遵循父类的契约,不改变父类方法的预期行为。
2.3 依赖倒转原则DIP
是开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体细节。高层模块不应该依赖低层模块,两者都应该依赖抽象。通过依赖抽象(接口 / 抽象类)降低模块间耦合。
2.4 接口隔离原则ISP
降低依赖、降低耦合。一个类对另外一个类的依赖性应当是建立在最小的接口上的,将胖接口拆分为多个小接口,避免客户端依赖无关方法。
2.5 迪米特法则(最少知道原则)LoD
一个对象应该对其他对象有最少的了解,应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
2.6 单一职责原则SRP
一个类应该只有一个引起它变化的原因。一个类只负责一项职责,避免将过多功能耦合在一起。
3.范围
总共有23种设计模式,分为创建型、结构型和行为型三种类型。
3.1 创建型
创建型是一种用于创建对象的设计模式,同时隐藏创建逻辑而不是使用new运算符直接实例化对象的模式,抽象了实例化的过程,创建对象时更加灵活。创建型设计模式一共有以下5种:
- 工厂模式 Factory method
- 单例模式 SingleTon
- 抽象工厂模式 Abstract factory
- 原型模式 Prototype
- 建造者模式 Builder
3.2 结构型
结构型是一种用于定义和组织类和对象的设计模式,关注对象之间的组合和关系,旨在解决如何构建灵活且可复用的类和对象结构。为解决怎样组装现有的类,设计它们的交互方式,从而达到实现一定的功能目的。结构型设计模式一共有以下7种:
- 适配器模式 Adapter
- 桥接模式 Bridge
- 组合模式 Composite
- 装饰模式 Decorator
- 外观模式 Facade
- 享元模式 Flyweight
- 代理模式 Proxy
3.3 行为型
行为型是一种用于定义对象之间的交互和行为的设计模式,关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。行为型设计模式一共有以下11种:
- 责任链模式 Chain of responsibility
- 命令模式 Command
- 解释器模式 Interpreter
- 迭代器模式 Iterator
- 中介模式 Mediator
- 备忘录模式 Memento
- 观察者模式 Observer
- 状态模式 State
- 策略模式 Strategy
- 模板方法模式 Template method
- 访问者模式 Visitor
二、Python常用的五大设计模式
1.工厂模式
定义:通过工厂类创建对象,隐藏具体实现细节,提高代码灵活性,实现创建逻辑和使用逻辑的解耦。工厂模式提供了一种创建对象的方式,而无需指定要创建的具体类,属于创建型设计模式。
类型:
- 简单工厂模式(静态工厂方法模式):只有一个工厂类负责所有具体产品的创建,通过静态方法或普通方法根据传入的参数决定创建哪种产品类的实例(而不是根据继承或多态),不属于GoF种设计模式,属于工厂模式变体(最简单常见);
- 工厂方法模式:定义一个创建对象的接口,让子类决定实例化哪一个类,使一个类的实例化延迟到其子类,是GoF设计模式之一;
- 抽象工厂模式:提供一个一系列相关或相关依赖对象的接口,而无须指定它们具体的类,是GoF设计模式之一;
优点:
- 代码解耦:将对象的创建与使用分离;
- 单一职责原则:将创建逻辑集中在一个地方;
- 开闭原则:易于扩展新的产品类型;
- 可维护性:代码结构清晰,易于维护
缺点:
- 复杂性增加:需要引入许多额外的类和接口;
- 过度设计:对于简单对象创建可能显得过于复杂;
- 抽象层增加:可能需要通过工厂来创建工厂,增加了理解难度;
代码示例:
# 简单工厂模式,实现日志记录器
from abc import ABC, abstractmethod
# 抽象产品
class Logger(ABC):
@abstractmethod
def log(self, message: str):
pass
# 具体产品类
class FileLogger(Logger):
def log(self, message: str):
print(f"将日志写入到文件:{message}")
class ConsoleLogger(Logger):
def log(self, message: str):
print(f"终端输出日志:{message}")
class DatabaseLogger(Logger):
def log(self, message: str):
print(f"将日志写入到远程服务器:{message}")
# 简单工厂,只有一个工厂类LoggerFactory负责所有具体产品的创建
class LoggerFactory:
@staticmethod
def create_logger(log_type: str) -> Logger:
if log_type.lower() == 'file':
return FileLogger()
elif log_type.lower() == 'console':
return ConsoleLogger()
elif log_type.lower() == 'database':
return DatabaseLogger()
else:
raise ValueError(f"未知的日志类型: {log_type}")
# 实例化
logger = LoggerFactory.create_logger("file")
logger.log("这是一个测试日志") # Output: 将日志写入到文件:这是一个测试日志
# 2.工厂方法模式,实现日志记录器
from abc import ABC, abstractmethod
# 抽象产品
class Logger(ABC):
@abstractmethod
def log(self, message: str):
pass
# 具体产品类
class FileLogger(Logger):
def log(self, message: str):
print(f"将日志写入到文件:{message}")
class ConsoleLogger(Logger):
def log(self, message: str):
print(f"终端输出日志:{message}")
class DatabaseLogger(Logger):
def log(self, message: str):
print(f"将日志写入到远程服务器:{message}")
# 抽象工厂类
class LoggerFactory(ABC):
@abstractmethod
def create_logger(self) -> Logger:
pass
# 具体工厂类(每个产品对应一个工厂子类),将对象的创建延迟到子类实现
class FileLoggerFactory(LoggerFactory):
def create_logger(self) -> Logger:
return FileLogger()
class ConsoleLoggerFactory(LoggerFactory):
def create_logger(self) -> Logger:
return ConsoleLogger()
class DatabaseLoggerFactory(LoggerFactory):
def create_logger(self) -> Logger:
return DatabaseLogger()
# 使用示例,
def client_code(factory: LoggerFactory):
logger = factory.create_logger()
logger.log("这是一个测试日志")
# 创建文件日志
file_factory = FileLoggerFactory()
client_code(file_factory) # Output: 将日志写入到文件:这是一个测试日志
# 创建控制台日志
console_factory = ConsoleLoggerFactory()
client_code(console_factory) # Output: 终端输出日志:这是一个测试日志
抽象工厂模式如果只有一个产品族也就是日志记录器的话,代码和工厂方法模式几乎完全相同。抽象工厂模式的核心价值在于管理多个相关联的产品族,一般是多个产品族才需要用到抽象工厂模式。
2.单例模式
定义:确保类中只有一个实例,并提供了一个全局访问点来访问该实例。
在Flask中,有许多扩展默认使用单例模式。单例模式主要包含三个要点:
- 私有化构造函数(防止外部实例化)
- 静态私有成员变量保存唯一实例
- 静态公有方法提供全局访问点
实现方式:基于模块、基于装饰器、基于元类metaclass、线程局部单例、基于闭包、基于类装饰器、双重检查锁、懒汉式、饿汉式、 Flask框架中的特殊实现等。
优点:
- 严格控制实例数量,节约系统资源,减少内存开销;
- 提供全局访问点,方便管理共享资源;
- 避免频繁创建销毁对象,提高性能;
缺点:
- 违反单一职责原则(既管理生命周期又包含业务逻辑),一个类应该只关心内部逻辑,而不关心实例化方式;
- 难以进行单元测试(全局状态影响测试隔离性);
- 在多线程环境下需要特殊处理线程安全问题;
代码实现:
# 1.基于模块实现
# test1.py
class _Singleton:
"""内部使用的单例类"""
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
instance = _Singleton() # 提供清晰的全局访问点
# 在其他的文件,通过模块导入
from test1 import instance
instance.increment()
print(f"ins1:{instance.value}") # Output: ins1:1
instance.increment()
print(f"ins2:{instance.value}") # Output: ins2:2
# 2.基于装饰器实现,线程安全则需要额外加锁
from functools import wraps
from threading import Lock
def singleton(cls):
"""单例装饰器"""
_instances = {} # 局部变量,保存类的单例实例
_lock = Lock() # 若需要加锁
# (1).不加锁
# @wraps(cls) # 保留原始类的元信息
def get_instance(*args, **kwargs):
if cls not in _instances:
_instances[cls] = cls(*args, **kwargs) # 首次调用时创建实例
return _instances[cls]
# (2).加锁
# @wraps(cls) # 保留原始类的元信息
def get_instance(*args, **kwargs):
nonlocal _instances
if cls not in _instances:
with _lock:
if cls not in _instances: # 双重检验确保只有一个线程能创建实例
_instances[cls] = cls(*args, **kwargs)
return _instances[cls]
return get_instance
@singleton # 应用装饰器
class Singleton:
"""单例类(通过装饰器实现)"""
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
s1 = Singleton() # 首次调用创建实例
print(f"s1:{s1.value}") # Output: s1:0
s2 = Singleton() # 返回已有实例
s2.increment()
print(f"s1:{s1.value}, s2:{s2.value}") # Output: s1:1, s2:1
print(s1 is s2) # Output: True(是同一个实例)
# 3.基于metaclass元类,更安全
class SingletonType(type):
"""单例元类"""
_instances = {} # 是元类的类变量,保存类的单例实例,必须通过 cls 访问
def __call__(cls, *args, **kwargs):
if cls not in cls._instances: # 通过 cls 访问类变量
cls._instances[cls] = super().__call__(*args, **kwargs) # 创建实例
return cls._instances[cls]
class Singleton(metaclass=SingletonType):
"""单例类(通过元类实现)"""
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
# 使用方式
s1 = Singleton() # 首次调用创建实例
print(f"s1:{s1.value}") # Output: s1:0
s2 = Singleton() # 返回已有实例
s2.increment()
print(f"s1:{s1.value}, s2:{s2.value}") # Output: s1:1, s2:1
print(s1 is s2) # Output: True(是同一个实例)
为什么元类版本更安全?
1.类变量共享:
_instances 是元类的类变量,所有子类共享同一个存储字典。
2.元类 _call_ 的原子性:
Python 的类创建机制(涉及元类时)默认是线程安全的,因为 GIL 会保证元类操作的原子性。
这种元类实现是 Python 单例模式中最健壮的方式之一,广泛应用于 Django ORM 等框架中。
3.装饰(器)模式
定义:在不修改原有代码的基础上增加额外的功能,动态扩展对象功能,通过组合而非继承实现灵活的功能增强,避免继承操作。
装饰器模式属于结构型设计模式,遵循开闭原则。通过将对象包装在一个装饰器类中,以便动态地修改其行为。装饰器可以接受无参数和带参数形式。当多个装饰器堆叠一起使用时,会按照从下到上的顺序依次使用。
实现方式:
- 函数装饰器语法(Python特有,通过高阶函数+闭包实现),这个更加常见;
- 类装饰器模式(面向对象风格,通过类继承实现);
内置装饰器:
- @staticmethod: 将方法定义为静态方法,不需要实例化类即可调用;
- @classmethod: 将方法定义为类方法,第一个参数是类本身(通常命名为 cls);
- @property: 将方法转换为属性,使其可以像属性一样访问;
使用场景:
- 日志记录:记录函数调用信息;
- 性能测试:统计函数执行时间;
- 权限校验:检查用户权限后再执行函数;
- 缓存(如 LRU Cache):@functools.lru_cache 是 Python 内置的装饰器;
- 路由注册:Web 框架(如 Flask)用 @app.route 装饰器注册路由;
代码示例:
# 1.性能测试,打印函数耗时(无参数)
from functools import wraps
import time
def timing_decorator(func):
@wraps(func)
def inner_func(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
return result
return inner_func
# 通过@使用装饰器
@timing_decorator
def test_decorator():
time.sleep(10)
test_decorator() #Output: test_decorator took 10.0016 seconds
# 2.使用类装饰器实现带状态的缓存装饰器
class CacheDecorator:
def __init__(self, func):
self.func = func
self.cache = {} # 用字典缓存结果
def __call__(self, *args):
if args in self.cache:
return self.cache[args]
result = self.func(*args)
self.cache[args] = result
return result
@CacheDecorator
def compute(x):
print(f"计算 {x} 的平方...")
return x * x
print(compute(3)) # 第一次计算 Output: 计算 3 的平方... 9
print(compute(3)) # 第二次直接返回缓存 Output: 9
4.策略模式
定义:定义一系列算法并封装它们,使它们可以灵活地相互替换,使用组合而非继承的方式,让算法的变化独立于使用它的客户端。策略模式是一种行为型设计模式,优点是算法切换自由、避免多重条件判断、扩展性好,缺点是策略类数量增多、所有策略类都需要暴露。
另外,由于Python中函数是一等公民,可以选择使用类或者函数来实现策略模式。
主要角色:
- Context(上下文): 维护一个对Strategy对象的引用,通过Strategy接口与具体策略交互;
- Strategy(策略接口): 定义所有支持的算法或行为的公共接口;
- ConcreteStrategy(具体策略): 实现Strategy接口的具体算法或行为;
使用场景:
- 多种算法/行为需要动态切换的场景,如支付系统中不同支付方式、导航系统中不同路线计算方式等;
- 需要替换复杂条件判断的场景,如电商折扣系统中不同用户类型的折扣策略等;
- 需要隔离算法实现的场景,如数据导出系统中不同数据导出的格式等;
- 需要灵活扩展的场景,如日志记录系统中不同日志输出方式等;
- 需要动态配置行为的场景,如缓存系统中不同缓存淘汰策略等;
代码实现:
# 1.使用抽象基类来实现折扣策略(更规范)
from abc import ABC, abstractmethod
# Strategy接口,定义所有支持的算法或行为的公共接口
class DiscountStrategy(ABC):
@abstractmethod
def apply_strategy(self, price: float) -> float:
pass
# ConcreteStrategy接口,实现具体的算法或行为
class NoDiscount(DiscountStrategy):
def apply_strategy(self, price: float) -> float:
return price
class VipDiscount(DiscountStrategy):
def apply_strategy(self, price: float) -> float:
return price * 0.9
class SVipDiscount(DiscountStrategy):
def apply_strategy(self, price: float) -> float:
return price * 0.8
class EmployeeDiscount(DiscountStrategy):
def apply_strategy(self, price: float) -> float:
return price * 0.7
# Context上下文,保存对 Strategy 对象的引用,供客户端调用和动态更换
class Order:
def __init__(self, price: float, discount_strategy: DiscountStrategy = NoDiscount()):
self.price = price
self.discount_strategy = discount_strategy
def final_price(self) -> float:
return self.discount_strategy.apply_strategy(self.price)
order1 = Order(100) # 默认NoDiscount
print(f"order1的最终价格:{order1.final_price()}") # Output: order1的最终价格:100
order2 = Order(100, VipDiscount())
print(f"order2的最终价格:{order2.final_price()}") # Output: order2的最终价格:90.0
# 运行时切换策略
order2.discount_strategy = EmployeeDiscount()
print(f"order2切换策略后的最终价格:{order2.final_price()}") # Output: order2切换策略后的最终价格:70.0
# 2.不使用抽象基类实现,鸭子类型更简单,但是缺点就是错误在运行时才能发现
# 通过方法约定的隐式接口代替了显式的抽象基类接口
# ConcreteStrategy接口,实现具体的算法或行为
class NoDiscount:
def apply_strategy(self, price: float) -> float:
return price
class VipDiscount:
def apply_strategy(self, price: float) -> float:
return price * 0.9
class SVipDiscount:
def apply_strategy(self, price: float) -> float:
return price * 0.8
class EmployeeDiscount:
def apply_strategy(self, price: float) -> float:
return price * 0.7
# Context上下文,保存对 Strategy 对象的引用,供客户端调用和动态更换
class Order:
def __init__(self, price: float, discount_strategy=NoDiscount()):
self.price = price
self.discount_strategy = discount_strategy
def final_price(self) -> float:
return self.discount_strategy.apply_strategy(self.price)
order1 = Order(100) # 默认NoDiscount
print(f"order1的最终价格:{order1.final_price()}") # Output: order1的最终价格:100
order2 = Order(100, VipDiscount())
print(f"order2的最终价格:{order2.final_price()}") # Output: order2的最终价格:90.0
# 运行时切换策略
order2.discount_strategy = EmployeeDiscount()
print(f"order2切换策略后的最终价格:{order2.final_price()}") # Output: order2切换策略后的最终价格:70.0
# 3.使用函数代替类
def no_discount(price: float) -> float: return price
def vip_discount(price: float) -> float: return price * 0.9
def svip_discount(price: float) -> float: return price * 0.8
def employee_discount(price: float) -> float:
return price * 0.7
# Context上下文,保存对 Strategy 对象的引用,供客户端调用和动态更换
class Order:
def __init__(self, price: float, discount_strategy=no_discount):
self.price = price
self.discount_strategy = discount_strategy
def final_price(self) -> float:
return self.discount_strategy(self.price)
order1 = Order(100) # 默认NoDiscount
print(f"order1的最终价格:{order1.final_price()}") # Output: order1的最终价格:100
order2 = Order(100, vip_discount)
print(f"order2的最终价格:{order2.final_price()}") # Output: order2的最终价格:90.0
# 运行时切换策略
order2.discount_strategy = employee_discount
print(f"order2切换策略后的最终价格:{order2.final_price()}") # Output: order2切换策略后的最终价格:70.0
5.观察者模式
定义:定义对象间的一对多依赖关系,当一个对象(被观察者/主题)状态改变时,自动通知所有依赖它的对象(观察者)并执行更新操作。
观察者是一种行为型设计模式,通过将主题和观察者解耦,实现了对象之间的松耦合。当主题的状态发生改变时,所有依赖于它的观察者都会收到通知并进行相应的更新。
核心组成:
- Subject 被观察者/主题:它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法;
- Observer 观察者:观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作;
- Concrete Subject具体主题:具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者;
- Concrete Observer具体观察者:具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作;
优点:
- 松耦合:主题和观察者之间松耦合,可以独立变化;
- 动态关系:可以在运行时动态添加或删除观察者;
- 广播通信:主题可以一次通知多个观察者;
缺点:
- 通知顺序不可控:观察者被通知的顺序是随机的;
- 性能问题:如果观察者过多或处理时间过长,可能导致性能问题;
- 循环依赖:不恰当的实现可能导致循环调用;
典型应用场景:
- 事件驱动系统:如GUI按钮点击、键盘输入事件;
- 状态监控:传感器数据变化、系统健康状态监测;
- 发布/订阅系统:消息队列、事件总线;
- 数据同步:数据库主从复制、缓存一致性维护;
- 工作流触发:订单状态变更触发后续操作;
代码示例:
from abc import ABC, abstractmethod
# 定义一个观察者接口,需要实现一个更新方法
class Observer(ABC):
"""接收主题通知"""
@abstractmethod
def update(self, subject):
pass
# 定义具体观察者,实现了更新方法,定义了在收到主题通知时需要执行的具体操作
class Display(Observer):
def update(self, subject):
print(f"温度更新: {subject.temperature}℃")
# 主题基类,维护一个观察者列表,提供了添加、删除和通知观察者的方法
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer: Observer):
"""添加观察者"""
if observer not in self._observers:
self._observers.append(observer)
def detach(self, observer: Observer):
"""删除观察者"""
if observer in self._observers:
self._observers.remove(observer)
def notify(self):
"""通知观察者"""
for observer in self._observers:
observer.update(self)
# 具体主题
class TemperatureSensor(Subject):
def __init__(self):
super().__init__()
self._temperature = 0
@property
def temperature(self):
return self._temperature
@temperature.setter
def temperature(self, value):
self._temperature = value
self.notify() # 值变化时自动通知
# 实例化具体主题类和具体观察者类
sensor = TemperatureSensor()
display = Display()
sensor.attach(display) # 添加观察者,建立观察者与主题的关联
# 属性赋值,温度为30
sensor.temperature = 30 # Output: 温度更新: 30℃
@property是属性装饰器,它将方法装换为“属性访问”形式,利用property机制,通过setter自动触发notify(),@temperature.setter是属性设置器,在属性赋值时执行额外的逻辑。
延伸补充:
1.父类parent class vs 元类 metaclass
父类和元类是完全不同的概念。
特性 | 父类(继承) | 元类 |
---|---|---|
作用对象 | 类的实例(对象) | 类本身(类的类) |
关系 | class Child(Parent) | class MyClass(metaclass=Meta) |
方法继承 | 子类继承父类的方法 | 控制类的创建和行为(如 __call__) |
典型用途 | 代码复用、多态 | 单例模式、ORM 框架、接口验证 |
关键区别:
- 父类影响实例的行为。
- 元类影响类的行为(如何创建类、如何实例化等)
默认元类: type
python所有类的默认元类都是type,是Python中最高层的元类。除了默认元类type,Python还有一些其他的内置元类,如创建抽象基类(ABCs)abc.ABCMeta、创建枚举类enum.EnumMeta等。
元类的实例范围:
- 普通类
- 其他元类(如果元类继承另一个元类)
- type自身(因为type也是自身的实例)
可以用isinstance(a,b)来验证对象a是不是类b或其子类的实例(返回True是,False否),是python中的类型检查的推荐方式,支持继承,兼容抽象基类和鸭子类型。
2.super()无参数形式 和 super(type, obj)带参数形式
super()指的是用于调用父类(超类)的方法,主要解决多重继承时的方法调用顺序问题(MRO,Method Resolution Order)。分为无参数形式、有参数形式以及在元类中的super()。
无参数形式super():在类的实例方法中,super()会自动绑定当前类和实例(相当于super(当前类, self)。调用super().method的时候会按照方法解析顺序MRO找到父类中定义的method并调用。
有参数形式super(type, obj):1). 在类方法中,type 是当前类,obj 是实例或类对象(取决于方法类型)。2).在实例方法中,type 是当前类,obj 是实例。python3中可以省略参数,优先使用super()无参数,除非需要特殊控制。
在元类中的super():super().__call_(*args, **kwargs)相当于type.__call_(cls, *args, **kwargs) # 调用元类的父类(通常是 type)的 __call__。作用是执行默认的实例化流程(_new_ + _init_),通常指向type的默认实现。
需要注意的是:super()的调用是按MRO方法解析顺序逐级向上的,不会自动触发所有父类的调用。多继承中super()的链式调用是协作式的,需要每个父类主动传递调用。
3. __call__()魔术方法
作用:让一个类的实例可以像函数一样被调用。
在元类中的意义:元类的 _call_ ()拦截类的实例化过程,super().__call__() 触发默认的实例化逻辑。
4. __init_() VS __new_()区别
在Python中,这两个方法是两个与对象创建相关的特殊方法。注意是先new后init。核心区别:
特性 | __new__() | __init__()构造方法 |
---|---|---|
方法类型 | 类方法(隐式) | 实例方法 |
调用时机 | 对象创建时,先于__init__调用 | 对象初始化时,在__new__后调用 |
返回值 | 必须返回一个实例对象(可以是其他类) | 始终返回None(不能有返回值) |
控制能力 | 可拦截对象创建(如单例) | 仅能设置已创建实例的属性 |
继承注意 | 需调用父类的__new__ | 通常无需显式调用父类的__init__ |
用途 | 控制对象的创建过程(如单例模式) | 初始化实例的属性 |
5.@classmethod vs @staticmethod
在 Python 中,@classmethod和@staticmethod是两种不同的方法装饰器,它们改变了方法的调用方式和行为。核心区别:
特性 | @classmethod | @staticmethod |
---|---|---|
绑定对象 | 绑定到类,而非实例 | 不绑定到类或实例 |
第一个参数 | cls(类本身) | 无特殊参数 |
调用方式 | 可通过类或实例调用 | 可通过类或实例调用 |
访问权限 | 可访问 / 修改类属性,调用其他类方法 | 无法访问类或实例的属性 / 方法 |
用途 | 工厂方法、类初始化逻辑 | 与类相关但无需访问类状态的工具函数 |
6.单例模式之懒汉式和饿汉式的区别
单例模式都禁止直接实例化!!
懒汉式:
- 特点:首次访问时才初始化实例(延迟加载);
- 优点:按需加载,节省资源(只有使用时才创建);
- 缺点:需要处理线程安全问题(多线程可能重复创建实例);
- 使用场景:实例初始化成本高,重量级对象或可能不用的服务。
代码示例:
# 基础版懒汉式(不加锁)需要处理线程安全问题,这里加了双重检查锁
import threading
class LazySingleton:
_instance = None
_lock = threading.Lock() # 加锁
def __init__(self):
if LazySingleton._instance is not None:
raise Exception("请使用 get_instance() 获取实例!")
self.id = id(self) # 存储实例ID
@classmethod
def get_instance(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None: # 双重检验
cls._instance = LazySingleton()
return cls._instance
def __str__(self):
return f"LazySingleton实例 (ID: {self.id})"
# 测试输出
s1 = LazySingleton.get_instance()
print(s1) # Output: LazySingleton实例 (ID: 2226695077360)
# 测试线程安全
def test_singleton():
instance = LazySingleton.get_instance()
print(id(instance)) # Output: 2226695077360 所有线程输出的 id 应该相同
# 启动多个线程测试
threads = []
for _ in range(5):
t = threading.Thread(target=test_singleton)
threads.append(t)
t.start()
for t in threads:
t.join()
饿汉式:
- 特点:类加载时(通常是作为模块导入)立即初始化,无论是否使用;
- 优点:天然线程安全(类加载机制保证);
- 缺点:可能浪费内存(实例未被使用时也占用内存);
- 使用场景:实例初始化成本低,轻量级对象,肯定会被使用的服务。
代码示例
# 模块级饿汉式单例模式
# eager_singleton.py
class _EagerSingleton:
def __str__(self):
return f"EagerSingleton实例 (ID: {id(self)})"
eager_instance = _EagerSingleton() # 模块加载时创建实例,此行代码在import执行
# 在另一个文件导入模块
from eager_singleton import eager_instance
print(eager_instance)