🐸 青蛙编程网
首页 / Python / 设计模式 / 2. 创建型模式:单例与工厂

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__ 拦截备着用;创建分支别乱撒,收进工厂一处理。

动手改

  1. 给 AppConfig 补一个 db_host 属性,验证 a 和 b 读到的是同一个值
  2. 给工厂加一个 "sms" 渠道:新建 SmsNotifier 类 + 工厂表加一行,调用方代码不用动
  3. 试试 create_notifier("fax"),观察抛出的 ValueError

下一章:创建型模式:建造者与原型 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。