2. 创建型模式:单例与工厂
这是什么:单例 = 全项目只有一个实例;工厂 = 把"创建哪个对象"这件事交给一个专门函数(或类)来做。两者都是创建型模式:管好对象的出生。
为什么用:配置中心、数据库连接池这类对象,创建两次就出两个配置,谁改的都算数, bug 查半天;而代码里到处 if kind == "chat": ChatNotifier() ... 重复判断,新增一种渠道就要改一串地方。单例保证"只有一个",工厂把创建逻辑收拢到一处。
如何用:先写一个配置类感受单例的两种 Python 写法,再用工厂函数收拢对象创建的分支。
单例写法一:模块就是单例
Python 的模块在第一次 import 时执行一次、之后全项目共用同一份——把实例放在模块顶层,天然就是单例,这也是最地道的写法:
# ---- 文件 config.py ----
class _Config:
def __init__(self):
self.db_host = "localhost"
self.debug = True
config = _Config() # 模块导入时创建,全项目共享同一份
# ---- 文件 main.py ----
import config
config.debug = False # A 处改
print(config.debug) # B 处读,读到的是改过的
你会看到:
False
A 处改、B 处读到的是同一个对象——不需要任何模式代码,Python 帮你做完了。
单例写法二:__new__ 拦截
如果要求"必须通过类来拿实例"(比如别人不 import 你的模块、直接用你的类),可以拦在 __new__ 上:实例只造一次,之后每次都返回同一份。
class AppConfig:
_instance = None # 记住唯一的那份实例
def __new__(cls):
if cls._instance is None: # 第一次:真的创建
cls._instance = super().__new__(cls)
cls._instance.debug = True
return cls._instance # 之后每次:返回老的那份
a = AppConfig()
b = AppConfig()
print(a is b)
a.debug = False
print(b.debug)
你会看到:
True
False
a is b 为 True——两个变量指向同一个实例,a 改了 b 跟着变。
工厂:把创建的 if 分支收拢到一处
通知功能要支持群聊、邮件多种渠道,调用方不该关心"每种的类怎么拼出来"——把这个决定交给工厂函数:
class ChatNotifier:
def send(self, msg):
print(f"[群聊] {msg}")
class MailNotifier:
def send(self, msg):
print(f"[邮件] {msg}")
def create_notifier(kind): # 工厂:名字进,对象出
table = {"chat": ChatNotifier, "mail": MailNotifier}
if kind not in table:
raise ValueError(f"不支持的渠道:{kind}")
return table[kind]()
for kind in ("chat", "mail"):
create_notifier(kind).send("服务器上线了")
你会看到:
[群聊] 服务器上线了
[邮件] 服务器上线了
调用方只认 create_notifier("chat"),不认识任何具体类。以后新增"短信渠道",只要在工厂表里加一行,所有调用代码零改动。
本章解读
| 代码 | 作用 |
|---|---|
config = _Config()(模块顶层) |
地道单例:import 时创建,全项目一份 |
cls._instance + __new__ |
类级单例:第一次创建,之后都返回同一份 |
a is b |
判断是不是同一个对象(不是 == 比内容) |
create_notifier(kind) |
工厂函数:调用方不 new 类,只报名字 |
table[kind]() |
从字典取类再调用——类也是对象,可以存进字典 |
记忆口诀
模块顶层天生单例,
__new__拦截备着用;创建分支别乱撒,收进工厂一处理。
动手改
- 给
AppConfig补一个db_host属性,验证a和b读到的是同一个值 - 给工厂加一个
"sms"渠道:新建SmsNotifier类 + 工厂表加一行,调用方代码不用动 - 试试
create_notifier("fax"),观察抛出的ValueError
下一章:创建型模式:建造者与原型 🐸