site logo

Marico's space

Python 设计模式:Factory、Singleton、Observer

编程技术 2026-07-15 11:28:46 4

不知道你有没有遇到过这种情况:代码写着写着,突然发现自己在一个文件里写了20个if/else,全是对象创建逻辑;或者改了个配置,全局搜索半天发现到处都在new实例;再或者产品说"加个新功能,要通知到所有相关模块",然后你得在十几处地方手动加代码。这些坑我都踩过,而且踩得还挺惨的。

后来才明白,这些不是自己菜,是缺设计模式。Python语法灵活,上手快,但如果没有Factory、Singleton、Observer这些套路,代码很容易变成一坨浆糊——改一处坏三处,测试写不动,需求来了只能硬着头皮往上堆。今天把这三个最实用的模式说清楚,配合能跑的代码,你看完就能往项目里搬。

为什么设计模式在Python里依然重要

有些人觉得Python是动态语言,设计模式没必要——这话对了一半。Python确实比Java/C++灵活很多,但设计模式解决的不是语法问题,是组织代码结构的问题。好的模式让你的代码:

  • 意图明确:说"这里用了Factory",同事秒懂你在干嘛
  • 减少重复:不用到处copy对象创建逻辑
  • 方便扩展:新需求来了,改一处比改十处安全多了

今天要聊的三个模式分两类:

  • 创建型模式(Factory、Singleton):管对象怎么出生
  • 行为型模式(Observer):管对象之间怎么通信

当你发现代码里if/else泛滥、到处new实例、或者改个数据要手动通知一堆模块的时候,就是该用这些模式的时候了。

Factory模式:把对象创建封装起来

Factory模式的核心就一句话:把"决定创建哪个类"的逻辑从业务代码里抽离出来。你可能在用户系统里见过这种代码:

if user_type == "学生": user = Student()
elif user_type == "老师": user = Teacher()
elif user_type == "管理员": user = Admin()

这种写法有两个问题:第一,每加一个用户类型就得改这里;第二,业务代码和对象创建强耦合了。Factory模式就是来解决这个的。

实际场景

假设你在做一个在线教育平台,要根据用户角色返回不同的操作权限。以前可能这样写:

if user_type == "student": user = Student()
elif user_type == "teacher": user = Teacher()

加一个新角色就要改这段代码,违反了"开闭原则"——对扩展开放,对修改封闭。Factory帮你绕过这个。

Python实现

from abc import ABC, abstractmethod class User(ABC): @abstractmethod def greet(self): pass class Student(User): def greet(self): return "Hi, I'm a student!" class Teacher(User): def greet(self): return "Hello, I'm a teacher!" class UserFactory: def create_user(self, user_type: str) -> User: if user_type == "student": return Student() elif user_type == "teacher": return Teacher() raise ValueError(f"Unknown user type: {user_type}") # 用法
factory = UserFactory()
user = factory.create_user("student")
print(user.greet()) # Output: Hi, I'm a student!

看明白了吗?对象创建的逻辑全在UserFactory里,业务代码只管调create_user。如果明天要加"管理员"角色,只改Factory,不用动任何调用方。这就是解耦的力量。

Singleton模式:全局只有一个实例

Singleton解决的问题很直接:某些类在整个程序生命周期里只能有唯一一个实例。典型场景:

  • 数据库连接池:一个应用共用一个连接池就够了
  • 配置管理器:全公司配置文件就一份
  • 日志系统:整个项目写同一个日志文件

什么时候用

不是所有"全局变量"都该用Singleton。判断标准就一条:这个资源天然就是唯一的,还是你硬要它唯一? 数据库连接池是前者,简单全局变量是后者。如果你不确定,不用Singleton,先用普通单例,后期再改也来得及。

Python实现

class ConfigManager: _instance = None _initialized = False def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __init__(self): if not self._initialized: self.settings = {"debug": False, "timeout": 30} self._initialized = True def get_setting(self, key): return self.settings.get(key) # 用法
config1 = ConfigManager()
config2 = ConfigManager() print(config1 is config2) # True: 同一个实例!
config1.settings["debug"] = True
print(config2.get_setting("debug")) # True: 共享状态

几个关键点:__new__控制实例创建,保证只new一次;__init__里加个_initialized标记,防止重复初始化。很多人踩坑就是没加这个标记——每次get_instance都会触发init,数据就被覆盖了。

顺带说一句,Python里还有种更Pythonic的实现方式——用模块级别的全局变量。因为Python的import机制天然保证了模块只加载一次,效果和Singleton一样,代码还更简洁。不过对于需要懒加载或者参数化初始化的场景,上面的写法更灵活。

Observer模式:事件驱动的核心

Observer模式解决的是"一对多依赖"的问题:当一个对象状态变了,所有依赖它的对象都要自动更新。UI框架的消息通知、订单系统的状态推送、日志系统的多端同步——这些场景都用得上。

实际场景

想象一个IoT监控系统:温度传感器数据变了,仪表盘、告警模块、数据分析模块都要同步更新。不用Observer的话,你得在传感器代码里手动调一遍所有模块——耦合得一塌糊涂。用了Observer,传感器只管通知,订阅者自己处理。

Python实现

from abc import ABC, abstractmethod class Subject(ABC): def __init__(self): self._observers = [] def attach(self, observer): self._observers.append(observer) def detach(self, observer): self._observers.remove(observer) def notify(self): for observer in self._observers: observer.update(self) class Sensor(Subject): def __init__(self): super().__init__() self._value = 0 def set_value(self, value): self._value = value self.notify() def get_value(self): return self._value class Dashboard: def __init__(self, name): self.name = name def update(self, subject): print(f"{self.name} updated: {subject.get_value()}") # 用法
sensor = Sensor()
dashboard1 = Dashboard("仪表盘1")
dashboard2 = Dashboard("仪表盘2") sensor.attach(dashboard1)
sensor.attach(dashboard2) sensor.set_value(42) # 两个仪表盘自动更新

核心就是三个方法:attach(订阅)、detach(退订)、notify(通知)。Subject不用知道有多少订阅者,不用关心订阅者是谁,只管调用notify就行。这就是松耦合——发送者和接收者都不知道对方的存在。

实际项目里用Observer要注意一个问题:通知是同步的,如果某个observer.update卡住了,后面的observer都得等着。解决方案是改成异步队列,或者用现成的消息队列中间件。不过对于大多数内部系统,上面的同步实现就够了。

实战组合:做一个日志系统

光说不练假把式,搞个实际能用的例子。把三个模式串起来用:一个日志系统,同时往控制台、文件、监控面板写日志。

import logging
from abc import ABC, abstractmethod # Singleton Logger
class Logger: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance.handlers = [] return cls._instance def add_handler(self, handler): self.handlers.append(handler) def log(self, message): for handler in self.handlers: handler.handle(message) # Factory for Handlers
class LogHandler(ABC): @abstractmethod def handle(self, message): pass class ConsoleHandler(LogHandler): def handle(self, message): print(f"[Console] {message}") class FileHandler(LogHandler): def handle(self, message): with open("logs.txt", "a") as f: f.write(f"[File] {message}\n") class HandlerFactory: def create_handler(self, type: str) -> LogHandler: if type == "console": return ConsoleHandler() elif type == "file": return FileHandler() raise ValueError(f"Unknown handler: {type}") # Observer for Dashboards
class LogDashboard: def __init__(self, name): self.name = name def update(self, message): print(f"{self.name} received: {message}") logger = Logger()
factory = HandlerFactory()
logger.add_handler(factory.create_handler("console"))
logger.add_handler(factory.create_handler("file")) dashboard = LogDashboard("主监控面板")
logger.handlers.append(dashboard) # 作为observer接入 logger.log("用户登录了")

跑一下试试,控制台、文件、监控面板三处同时收到日志。整个系统里Logger是单例,Handler通过工厂创建,Dashboard作为观察者自动接收通知——三个模式各司其职,代码清晰得一目了然。

如果要加新的Handler(比如微信告警、钉钉Webhook),只需要:实现一个新Handler类,然后在HandlerFactory里加一行判断。零修改现有代码,符合开闭原则。

写在最后

设计模式不是银弹,用错了反而增加复杂度。我的建议是:

  • 代码简单、需求稳定——别强行套模式,先跑起来
  • 重复代码超过3处、或者确定要加新功能——再考虑用Factory解耦
  • 全局唯一资源(数据库连接、配置)——用Singleton
  • 状态变化需要通知多方——用Observer

不要为了"显得专业"而用模式,模式是为问题服务的。先理解透每个模式解决什么问题,然后在合适的场景里自然地用出来,这比背概念重要得多。