当前位置:首页>python>Python教程 Episode 22 - Python 设计模式实战

Python教程 Episode 22 - Python 设计模式实战

  • 2026-10-11 05:40:34
Python教程 Episode 22 - Python 设计模式实战

欢迎来到"Python教程从零基础到实战"系列的第二十二期!

经过了前面二十一期,你已经掌握了 Python 的基础语法、数据结构、面向对象编程、爬虫、数据分析、Web 开发、部署运维和性能优化。但你有没有遇到过这样的代码——

```python

# 一个"功能完善"但结构混乱的配置管理模块

defget_config(key):

"""获取配置"""

# 先读环境变量

    val = os.environ.get(key)

if val:

return val

# 再读配置文件

# ...一堆 if/else...

# 再读数据库...

# ...又一堆逻辑...

return default

defset_config(key, value):

# 又要写环境变量、写文件、写数据库...

# 复制粘贴了无数行 get_config 的逻辑

pass

```

这段代码能跑,但维护起来非常痛苦。改一个逻辑要动到处函数,加一个新配置来源要改每一处。问题出在哪?**没有好的架构设计**。

这一期,我们来讲——**设计模式**。不照搬 textbook,我们用真实的 Python 项目来说明,每种模式怎么解决实际问题。

---

## 一、为什么需要设计模式?

### 1.1 设计模式的本质

设计模式不是"必须遵守的规则",而是**前辈们总结的、经过验证的解决方案模板**。它们回答的问题是:

> "当 A 情况发生时,用 B 方式来解决,能避免 C 问题。"

比如:**多个类需要共享同一个全局状态**——用**单例模式**,比在每次调用时手动判断"我是不是第一个创建的实例"要优雅得多。

### 1.2 Python 的特殊性

和其他语言不同,Python 有几个独特的优势:

-**一切皆对象**:函数也是对象,可以传递、返回、存储

-**动态特性**:可以在运行时修改类的行为

-**装饰器**:天然支持"在不修改源码的前提下增强功能"

这意味着很多在其他语言里需要用复杂套路实现的模式,在 Python 里有更简单的写法。但这不意味着设计模式没用——它们提供的**结构化思路**依然不可替代。

---

## 二、创建型模式:如何优雅地创建对象

### 2.1 单例模式(Singleton)

**场景**:配置管理器、日志器、数据库连接池——整个程序只需要一个实例。

#### 方案一:模块本身就是单例

```python

# config.py — Python 模块导入天然就是单例!

import os

classConfigManager:

"""配置管理器:确保全局只有一个实例"""

    _instance = None

def__new__(cls):

ifcls._instance isNone:

print("[Config] 创建新实例")

cls._instance = super().__new__(cls)

cls._instance._initialized = False

returncls._instance

def__init__(self):

ifself._initialized:

return# 已经初始化过了,跳过

self._initialized = True

self._config = {}

self._load_from_env()

self._load_from_file()

def_load_from_env(self):

"""从环境变量加载"""

        env_vars = {

"DATABASE_URL": os.getenv("DATABASE_URL"),

"API_KEY": os.getenv("API_KEY"),

"DEBUG": os.getenv("DEBUG", "false").lower() == "true",

        }

for key, value in env_vars.items():

if value isnotNone:

self._config[key] = value

def_load_from_file(self):

"""从 YAML 配置文件加载"""

try:

import yaml

withopen("config.yaml", "r", encoding="utf-8") as f:

                file_config = yaml.safe_load(f)

if file_config:

self._config.update(file_config)

exceptFileNotFoundError:

pass# 没有配置文件就跳过

exceptImportError:

# yaml 库不存在时使用 JSON

import json

try:

withopen("config.json", "r", encoding="utf-8") as f:

self._config.update(json.load(f))

exceptFileNotFoundError:

pass

defget(self, key, default=None):

returnself._config.get(key, default)

defset(self, key, value):

self._config[key] = value

def__repr__(self):

returnf"ConfigManager(config={dict(self._config)})"

# 使用

cfg1 = ConfigManager()

cfg2 = ConfigManager()

print(cfg1 is cfg2)  # True! 它们是同一个实例

cfg1.set("APP_NAME", "MyApp")

print(cfg2.get("APP_NAME"))  # "MyApp" — 通过另一个实例也能访问

```

#### 方案二:用装饰器实现

```python

from functools import wraps

defsingleton(cls):

"""装饰器版本:适用于任意类"""

    instances = {}

@wraps(cls)

defget_instance(*args, **kwargs):

ifclsnotin instances:

            instances[cls] = cls(*args, **kwargs)

return instances[cls]

return get_instance

@singleton

classDatabaseConnection:

def__init__(self, url: str):

self.url = url

print(f"[DB] 连接到 {url}")

defquery(self, sql: str) -> list:

print(f"[DB] 执行 SQL: {sql}")

return [{"id": 1, "name": "Alice"}]

db1 = DatabaseConnection("postgresql://localhost/mydb")

db2 = DatabaseConnection("postgresql://localhost/otherdb")  # ← 被忽略!

print(db1.url)  # "postgresql://localhost/mydb" — 参数只接受第一次的

```

#### 方案三:Pythonic 的做法(推荐)

```python

# 实际上,对于纯配置场景,最 Pythonic 的方式根本不需要单例:

# 直接用模块级别的变量就行

# settings.py

DATABASE_URL = "postgresql://localhost/mydb"

API_KEY = "sk-xxxxx"

DEBUG = False

# 在其他文件中直接导入

from settings importDATABASE_URL, DEBUG

# 或者作为命名空间

import settings

print(settings.DATABASE_URL)

```

**经验法则**:Python 中,如果功能能用模块级别变量实现,就别用单例。单例的真正战场在——需要"状态"的场景,比如日志器、指标采集器。

---

### 2.2 工厂模式(Factory)

**场景**:需要根据用户输入或配置,动态选择创建哪种类型的对象。

```python

from abc importABC, abstractmethod

import json

import yaml

classFormatter(ABC):

"""输出格式化器的抽象基类"""

@abstractmethod

defformat(self, data: dict) -> str:

pass

classJSONFormatter(Formatter):

defformat(self, data: dict) -> str:

return json.dumps(data, ensure_ascii=False, indent=2)

classCSVFormatter(Formatter):

defformat(self, data: dict) -> str:

ifnot data:

return""

        headers = list(data[0].keys())

        lines = [",".join(headers)]

for row in data:

            lines.append(",".join(str(row[h]) for h in headers))

return"\n".join(lines)

classMarkdownTableFormatter(Formatter):

defformat(self, data: dict) -> str:

ifnot data:

return""

        headers = list(data[0].keys())

        lines = ["| " + " | ".join(headers) + " |"]

        lines.append("| " + " | ".join("---"for _ in headers) + " |")

for row in data:

            lines.append("| " + " | ".join(str(row[h]) for h in headers) + " |")

return"\n".join(lines)

# ===== 工厂:负责创建正确的格式化器 =====

classFormatterFactory:

"""集中管理格式化器的创建"""

# 注册表:格式名 -> 格式化器类

    _formatters: dict[str, type] = {

"json": JSONFormatter,

"csv": CSVFormatter,

"markdown": MarkdownTableFormatter,

    }

@classmethod

defregister(cls, name: str, formatter_class: type):

"""允许外部注册新的格式化器"""

cls._formatters[name] = formatter_class

@classmethod

defcreate(cls, fmt: str) -> Formatter:

"""根据名字创建格式化器"""

if fmt notincls._formatters:

raiseValueError(

f"未知的格式 '{fmt}',支持的格式: {list(cls._formatters.keys())}"

            )

returncls._formatters[fmt]()

# 使用示例

users = [

    {"id": 1, "name": "张三", "role": "管理员"},

    {"id": 2, "name": "李四", "role": "普通用户"},

]

# 创建不同格式的输出

for fmt_type in ["json", "csv", "markdown"]:

    formatter = FormatterFactory.create(fmt_type)

print(f"\n=== {fmt_type.upper()} ===")

print(formatter.format(users))

# 扩展:用户可以注册自己的格式化器

classXMLFormatter(Formatter):

defformat(self, data: dict) -> str:

        lines = ["<?xml version='1.0' encoding='utf-8'?>", "<records>"]

for record in data:

            line = "  <record>"

for key, value in record.items():

                line += f"<{key}>{value}</{key}>"

            line += "</record>"

            lines.append(line)

        lines.append("</records>")

return"\n".join(lines)

FormatterFactory.register("xml", XMLFormatter)

xml_formatter = FormatterFactory.create("xml")

print(f"\n=== xml ===\n{xml_formatter.format(users)}")

```

---

### 2.3 建造者模式(Builder)

**场景**:构建一个复杂的 HTTP 请求、构建一份包含多个选项的报告、构建一个数据库查询。

```python

classHttpRequestBuilder:

"""链式调用的 HTTP 请求构建器"""

def__init__(self, method: str, url: str):

self.method = method

self.url = url

self._headers: dict[str, str] = {}

self._params: dict[str, str] = {}

self._body = None

defheader(self, key: str, value: str) -> "HttpRequestBuilder":

"""添加请求头——返回 self 以支持链式调用"""

self._headers[key] = value

returnself# ← 关键:返回自身

defparams(self, **kwargs) -> "HttpRequestBuilder":

"""添加 URL 查询参数"""

self._params.update(kwargs)

returnself

defjson_body(self, data: dict) -> "HttpRequestBuilder":

"""设置 JSON body"""

import json

self._body = json.dumps(data).encode("utf-8")

self.header("Content-Type", "application/json")

returnself

deftext_body(self, text: str) -> "HttpRequestBuilder":

"""设置纯文本 body"""

self._body = text.encode("utf-8")

self.header("Content-Type", "text/plain")

returnself

defbuild(self) -> dict:

"""构建最终请求"""

from urllib.parse import urlencode

        request = {

"method": self.method,

"url": self.url,

"headers": self._headers,

"body": self._body,

        }

ifself._params:

            query_string = urlencode(self._params)

            request["url"] += f"?{query_string}"

return request

# ===== 使用示例 =====

# 链式调用构建 GET 请求

get_request = HttpRequestBuilder("GET", "https://api.example.com/users").params(

page=1, per_page=10

).build()

print("GET 请求:", get_request)

# 链式调用构建 POST 请求

post_request = HttpRequestBuilder("POST", "https://api.example.com/users").header(

"Authorization", "Bearer my-token"

).json_body({"name": "王五", "email": "wang@example.com"}).build()

print("\nPOST 请求:", post_request)

# 复杂请求

complex_request = (

    HttpRequestBuilder("PUT", "https://api.example.com/users/42")

    .header("Authorization", "Bearer my-token")

    .header("If-Match", '"v1"')

    .json_body({"name": "王五 updated"})

    .build()

)

print("\nPUT 请求:", complex_request)

```

**什么时候用建造者?** 当你需要构建的对象有很多可选参数时,建造者比一堆可选参数的构造函数要清晰得多。

---

## 三、结构型模式:如何优雅地组合对象

### 3.1 适配器模式(Adapter)

**场景**:你的代码想用一个 API,但供应商提供了不同的接口。适配器帮你"翻译"。

```python

from abc importABC, abstractmethod

import smtplib

from email.mime.text import MIMEText

classEmailService(ABC):

"""我们的代码所期望的统一邮件服务接口"""

@abstractmethod

defsend(self, to: str, subject: str, body: str) -> bool:

pass

classSMTPAdapter(EmailService):

"""适配器:将统一接口适配到 SMTP 协议"""

def__init__(self, host: str, port: int, username: str, password: str):

self.host = host

self.port = port

self.username = username

self.password = password

defsend(self, to: str, subject: str, body: str) -> bool:

        msg = MIMEText(body, "html", "utf-8")

        msg["Subject"] = subject

        msg["From"] = self.username

        msg["To"] = to

try:

            server = smtplib.SMTP(self.host, self.port)

            server.starttls()

            server.login(self.username, self.password)

            server.sendmail(self.username, [to], msg.as_string())

            server.quit()

returnTrue

exceptExceptionas e:

print(f"SMTP 发送失败: {e}")

returnFalse

classSendGridAdapter(EmailService):

"""适配器:将统一接口适配到 SendGrid API"""

def__init__(self, api_key: str):

self.api_key = api_key

defsend(self, to: str, subject: str, body: str) -> bool:

import requests

        data = {

"personalizations": [{"to": [{"email": to}]}],

"from": {"email": "noreply@example.com"},

"subject": subject,

"content": [{"type": "text/html", "value": body}],

        }

try:

            resp = requests.post(

"https://api.sendgrid.com/v3/mail/send",

headers={

"Authorization": f"Bearer {self.api_key}",

"Content-Type": "application/json",

                },

json=data,

timeout=10,

            )

return resp.status_code == 202

exceptExceptionas e:

print(f"SendGrid 发送失败: {e}")

returnFalse

classAWSSESAdapter(EmailService):

"""适配器:适配 AWS SES"""

def__init__(self, region: str, access_key: str, secret_key: str):

self.region = region

self.access_key = access_key

self.secret_key = secret_key

defsend(self, to: str, subject: str, body: str) -> bool:

try:

import boto3

            ses = boto3.client("ses", region_name=self.region)

            ses.send_email(

Source=self.access_key + "@example.com",

Destination={"ToAddresses": [to]},

Message={

"Subject": {"Data": subject},

"Body": {"Html": {"Data": body}},

                },

            )

returnTrue

exceptExceptionas e:

print(f"AWS SES 发送失败: {e}")

returnFalse

# ===== 使用示例:上层业务代码完全不用关心底层实现 =====

defnotify_user(email_service: EmailService, user_email: str, message: str):

"""通知用户——只依赖抽象接口"""

    success = email_service.send(user_email, "系统通知", message)

if success:

print(f"  ✓ 通知已发送至 {user_email}")

else:

print(f"  ✗ 通知发送失败: {user_email}")

# 可以随时切换邮件服务,不需要修改 notify_user 的代码!

smtp_service = SMTPAdapter("smtp.gmail.com", 587, "user@gmail.com", "password")

notify_user(smtp_service, "admin@example.com", "<h1>服务器健康</h1><p>一切正常</p>")

# 切换到 SendGrid

sendgrid_service = SendGridAdapter("SG.xxxxx")

notify_user(sendgrid_service, "admin@example.com", "<h1>服务器健康</h1><p>一切正常</p>")

```

---

### 3.2 装饰器模式(Decorator)

你已经用过 Python 的 `@` 装饰器语法了,但那是**函数装饰器**——它修改的是函数本身。装饰器模式修饰的是**对象的行为**,两者概念不同但思路相通。

```python

from abc importABC, abstractmethod

classCoffee(ABC):

"""咖啡抽象类"""

@abstractmethod

defcost(self) -> float:

pass

@abstractmethod

defdescription(self) -> str:

pass

classSimpleCoffee(Coffee):

"""基础咖啡"""

defcost(self) -> float:

return10.0

defdescription(self) -> str:

return"简易咖啡"

# ===== 装饰器:给咖啡加料 =====

classCoffeeDecorator(Coffee):

"""咖啡装饰器基类"""

def__init__(self, coffee: Coffee):

self._coffee = coffee

defcost(self) -> float:

returnself._coffee.cost()

defdescription(self) -> str:

returnself._coffee.description()

classMilk(CoffeeDecorator):

"""牛奶装饰器"""

defcost(self) -> float:

returnself._coffee.cost() + 3.0

defdescription(self) -> str:

returnf"{self._coffee.description()} + 牛奶"

classWhippedCream(CoffeeDecorator):

"""奶油装饰器"""

defcost(self) -> float:

returnself._coffee.cost() + 2.0

defdescription(self) -> str:

returnf"{self._coffee.description()} + 奶油"

classSugar(CoffeeDecorator):

"""糖装饰器"""

defcost(self) -> float:

returnself._coffee.cost() + 1.0

defdescription(self) -> str:

returnf"{self._coffee.description()} + 糖"

# ===== 使用示例 =====

my_coffee = SimpleCoffee()

print(f"{my_coffee.description()}: ¥{my_coffee.cost():.1f}")

# 简易咖啡: ¥10.0

my_coffee = Milk(SimpleCoffee())

print(f"{my_coffee.description()}: ¥{my_coffee.cost():.1f}")

# 简易咖啡 + 牛奶: ¥13.0

my_coffee = WhippedCream(Milk(SimpleCoffee()))

print(f"{my_coffee.description()}: ¥{my_coffee.cost():.1f}")

# 简易咖啡 + 牛奶 + 奶油: ¥15.0

my_coffee = Sugar(WhippedCream(Milk(SimpleCoffee())))

print(f"{my_coffee.description()}: ¥{my_coffee.cost():.1f}")

# 简易咖啡 + 牛奶 + 奶油 + 糖: ¥16.0

```

Python 的函数装饰器和对象装饰器模式可以结合使用:

```python

from functools import wraps

import time

deftimer(func):

"""计时装饰器——测量函数执行时间"""

@wraps(func)

defwrapper(*args, **kwargs):

        start = time.perf_counter()

        result = func(*args, **kwargs)

        elapsed = time.perf_counter() - start

print(f"  [{func.__name__}] 耗时 {elapsed:.4f}s")

return result

return wrapper

defretry(max_retries: int = 3, delay: float = 1.0):

"""重试装饰器——失败时自动重试"""

defdecorator(func):

@wraps(func)

defwrapper(*args, **kwargs):

for attempt inrange(1, max_retries + 1):

try:

return func(*args, **kwargs)

exceptExceptionas e:

if attempt == max_retries:

raise

print(f"  ⚠ 第 {attempt} 次尝试失败: {e},{delay}s 后重试")

                    time.sleep(delay)

return wrapper

return decorator

# 组合使用

@timer

@retry(max_retries=3, delay=0.1)

defunstable_api_call(request_id: int) -> dict:

"""模拟一个偶尔会失败的 API 调用"""

import random

if random.random() < 0.3:

raiseConnectionError("网络连接不稳定")

return {"request_id": request_id, "data": "success"}

result = unstable_api_call(42)

print(f"  结果: {result}")

```

---

### 3.3 外观模式(Facade)

**场景**:子系统太复杂,提供一个简化的统一入口。

```python

import subprocess

import os

from datetime import datetime

classDockerOperations:

"""Docker 操作门面——把复杂的 docker 命令封装成简单方法"""

def__init__(self, project_dir: str = "."):

self.project_dir = project_dir

defbuild_image(self, image_name: str, tag: str = "latest") -> bool:

"""构建镜像"""

        cmd = f'docker build -t "{image_name}:{tag}" .'

print(f"  🔨 构建镜像: {cmd}")

        result = subprocess.run(cmd, shell=True, capture_output=True, text=True,

cwd=self.project_dir)

return result.returncode == 0

defrun_container(self, image_name: str, container_name: str,

ports: dict = None, detach: bool = True) -> bool:

"""运行容器"""

        cmd = f'docker run'

if detach:

            cmd += " -d"

if container_name:

            cmd += f' --name "{container_name}"'

if ports:

for host, container in ports.items():

                cmd += f' -p {host}:{container}'

        cmd += f' "{image_name}:latest"'

print(f"  ▶ 启动容器: {cmd}")

        result = subprocess.run(cmd, shell=True, capture_output=True, text=True,

cwd=self.project_dir)

return result.returncode == 0

defstop_container(self, container_name: str) -> bool:

"""停止容器"""

        cmd = f'docker stop "{container_name}"'

print(f"  ⏸ 停止容器: {cmd}")

        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)

return result.returncode == 0

defremove_container(self, container_name: str) -> bool:

"""删除容器"""

        cmd = f'docker rm "{container_name}"'

print(f"  🗑 删除容器: {cmd}")

        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)

return result.returncode == 0

defhealth_check(self, container_name: str) -> dict:

"""检查容器状态"""

import json

        cmd = f'docker inspect --format="{{{{json .State}}}}" "{container_name}"'

        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)

if result.returncode != 0:

return {"status": "not_found"}

        state = json.loads(result.stdout)

return {

"running": state.get("Running", False),

"status": state.get("Status", "unknown"),

"exit_code": state.get("ExitCode", -1),

"started_at": state.get("StartedAt", ""),

        }

# ===== 外观:一行搞定整个部署流程 =====

classDeploymentFacade:

"""部署外观——对外只暴露一个 deploy() 方法"""

def__init__(self, app_name: str, project_dir: str = "."):

self.app_name = app_name

self.docker = DockerOperations(project_dir)

self.tag = datetime.now().strftime("%Y%m%d-%H%M%S")

defdeploy(self, restart_if_exists: bool = True) -> bool:

"""一键部署:停旧容器 → 删旧容器 → 构建 → 启动"""

        container_name = f"{self.app_name}-app"

# 第一步:清理旧容器

if restart_if_exists:

try:

                health = self.docker.health_check(container_name)

if health["running"]:

print(f"\n  📦 发现运行的容器,先停止...")

self.docker.stop_container(container_name)

self.docker.remove_container(container_name)

exceptException:

pass# 容器不存在也没关系

# 第二步:构建新镜像

        image_name = f"{self.app_name}"

print(f"\n  🏷 标记新版本: {self.tag}")

ifnotself.docker.build_image(image_name, self.tag):

print("  ❌ 镜像构建失败!")

returnFalse

# 第三步:启动容器

print(f"\n  🚀 启动新版本容器...")

ifnotself.docker.run_container(

            image_name, container_name,

ports={8080: 8080, 8081: 8081}

        ):

print("  ❌ 容器启动失败!")

returnFalse

# 第四步:确认健康

        health = self.docker.health_check(container_name)

if health.get("running"):

print(f"\n  ✅ 部署成功!容器正在运行")

print(f"     访问 http://localhost:8080")

returnTrue

else:

print(f"\n  ⚠️ 容器启动但可能异常: {health}")

returnFalse

# 使用:一行完成部署

facade = DeploymentFacade("my-web-app")

facade.deploy()

```

---

## 四、行为型模式:对象之间如何协作

### 4.1 策略模式(Strategy)

**场景**:同一个操作有多种算法实现,运行时可以选择。比如排序算法、支付渠道、数据导出格式。

```python

from abc importABC, abstractmethod

import csv

import json

import os

classExportStrategy(ABC):

"""导出策略抽象类"""

@abstractmethod

defexport(self, data: list[dict], filepath: str) -> str:

"""导出数据到指定文件"""

pass

@abstractmethod

defextension(self) -> str:

"""返回文件扩展名"""

pass

classJSONExportStrategy(ExportStrategy):

defexport(self, data: list[dict], filepath: str) -> str:

withopen(filepath, "w", encoding="utf-8") as f:

            json.dump(data, f, ensure_ascii=False, indent=2)

return filepath

defextension(self) -> str:

return".json"

classCSVExportStrategy(ExportStrategy):

defexport(self, data: list[dict], filepath: str) -> str:

ifnot data:

withopen(filepath, "w", encoding="utf-8") as f:

                f.write("")

return filepath

        fieldnames = list(data[0].keys())

withopen(filepath, "w", encoding="utf-8", newline="") as f:

            writer = csv.DictWriter(f, fieldnames=fieldnames)

            writer.writeheader()

            writer.writerows(data)

return filepath

defextension(self) -> str:

return".csv"

classReportGenerator:

"""报告生成器:依赖策略,而非硬编码"""

def__init__(self, strategy: ExportStrategy):

self._strategy = strategy

defchange_strategy(self, strategy: ExportStrategy):

"""运行时切换策略"""

self._strategy = strategy

defgenerate_report(self, data: list[dict], output_dir: str = ".") -> str:

"""生成报告"""

        filepath = os.path.join(

            output_dir, f"report{self._strategy.extension()}"

        )

        exported_path = self._strategy.export(data, filepath)

return exported_path

# 使用

sales_data = [

    {"product": "iPhone 15", "quantity": 120, "revenue": 899000},

    {"product": "MacBook Pro", "quantity": 45, "revenue": 2245000},

    {"product": "AirPods Pro", "quantity": 300, "revenue": 359700},

]

# 默认导出为 JSON

generator = ReportGenerator(JSONExportStrategy())

path = generator.generate_report(sales_data)

print(f"JSON 报告: {path}")

# 切换为 CSV

generator.change_strategy(CSVExportStrategy())

path = generator.generate_report(sales_data)

print(f"CSV 报告: {path}")

```

**核心要点**:`ReportGenerator` 完全不关心导出的具体实现。新增一种导出格式只需要创建一个新策略类,不需要改动任何已有代码——符合**开闭原则**(对扩展开放,对修改封闭)。

---

### 4.2 观察者模式(Observer)

**场景**:事件总线、消息推送、UI 刷新、传感器数据订阅。一个对象状态变化时,所有关注者都要收到通知。

```python

classEventBus:

"""简单的事件总线"""

def__init__(self):

self._listeners: dict[str, list] = {}

defsubscribe(self, event_type: str, callback):

"""订阅某个事件"""

if event_type notinself._listeners:

self._listeners[event_type] = []

self._listeners[event_type].append(callback)

print(f"  👂 订阅: {event_type}")

defunsubscribe(self, event_type: str, callback):

"""取消订阅"""

if event_type inself._listeners:

self._listeners[event_type].remove(callback)

defpublish(self, event_type: str, data: dict = None):

"""发布事件"""

if data isNone:

            data = {}

if event_type inself._listeners:

for callback inself._listeners[event_type]:

                callback(event_type, data)

# ===== 实际场景:电商系统中的事件通知 =====

classOrderSystem:

"""订单系统——发布事件"""

def__init__(self, bus: EventBus):

self.bus = bus

self._orders = {}

self._order_counter = 0

defcreate_order(self, customer: str, items: list, amount: float) -> str:

"""创建订单并发布事件"""

self._order_counter += 1

        order_id = f"ORD-{self._order_counter:06d}"

self._orders[order_id] = {

"customer": customer,

"items": items,

"amount": amount,

"status": "pending",

        }

# 发布事件

self.bus.publish("order.created", {

"order_id": order_id,

"customer": customer,

"amount": amount,

        })

return order_id

classNotificationService:

"""通知服务——监听事件"""

def__init__(self, bus: EventBus):

        bus.subscribe("order.created", self.on_order_created)

        bus.subscribe("order.paid", self.on_order_paid)

defon_order_created(self, event_type: str, data: dict):

print(f"  📧 [通知服务] 新订单: {data['order_id']} ({data['customer']}, ¥{data['amount']})")

defon_order_paid(self, event_type: str, data: dict):

print(f"  💰 [通知服务] 订单支付成功: {data['order_id']}")

classInventoryService:

"""库存服务——监听事件"""

def__init__(self, bus: EventBus):

        bus.subscribe("order.created", self.on_order_created)

self.stock = {"iPhone 15": 100, "MacBook Pro": 50, "AirPods Pro": 200}

defon_order_created(self, event_type: str, data: dict):

        total_items = len(data["items"])

print(f"  📦 [库存服务] 新订单占用库存: {total_items} 件商品")

classAnalyticsService:

"""分析服务——监听事件"""

def__init__(self, bus: EventBus):

        bus.subscribe("order.created", self.on_order_created)

        bus.subscribe("order.paid", self.on_order_paid)

self.stats = {"total_orders": 0, "total_revenue": 0.0}

defon_order_created(self, event_type: str, data: dict):

self.stats["total_orders"] += 1

print(f"  📊 [分析服务] 订单计数更新: 总订单={self.stats['total_orders']}")

defon_order_paid(self, event_type: str, data: dict):

self.stats["total_revenue"] += data.get("amount", 0)

print(f"  📈 [分析服务] 收入统计更新: 总收入=¥{self.stats['total_revenue']:,.2f}")

# ===== 运行 =====

bus = EventBus()

order_system = OrderSystem(bus)

NotificationService(bus)

InventoryService(bus)

AnalyticsService(bus)

print("\n--- 创建订单 ---")

order_system.create_order(

customer="张三",

items=["iPhone 15", "AirPods Pro"],

amount=8590.00,

)

print("\n--- 模拟支付 ---")

bus.publish("order.paid", {"order_id": "ORD-000001", "amount": 8590.00})

```

---

### 4.3 责任链模式(Chain of Responsibility)

**场景**:请求需要经过多道审批/处理步骤。每条链路独立,可以灵活增删。

```python

from abc importABC, abstractmethod

from typing import Optional

classHandler(ABC):

"""处理器抽象类"""

def__init__(self):

self._next_handler: Optional[Handler] = None

defset_next(self, handler: "Handler") -> "Handler":

"""设置下一个处理器,返回它以便链式调用"""

self._next_handler = handler

return handler  # 返回下一个处理器本身

defhandle(self, request: dict) -> Optional[dict]:

"""处理请求"""

ifself._can_handle(request):

            result = self._process(request)

if result andself._next_handler:

returnself._next_handler.handle(result)

return result

elifself._next_handler:

returnself._next_handler.handle(request)

returnNone

def_can_handle(self, request: dict) -> bool:

"""判断能否处理此请求——子类覆盖"""

returnTrue

@abstractmethod

def_process(self, request: dict) -> Optional[dict]:

"""实际处理逻辑——子类覆盖"""

pass

classAuthHandler(Handler):

"""认证处理器"""

def_can_handle(self, request: dict) -> bool:

return"token"notin request

def_process(self, request: dict) -> Optional[dict]:

        token = request.get("token")

if token and token.startswith("valid_"):

            request["user"] = "authenticated_user"

print("  🔐 认证成功")

return request

        request["user"] = "anonymous"

print("  🔐 无有效 Token,标记为匿名用户")

return request

classPermissionHandler(Handler):

"""权限处理器"""

REQUIRED_PERMISSIONS = {

"/api/admin": "admin",

"/api/orders": "reader",

"/api/reports": "analyst",

    }

def_can_handle(self, request: dict) -> bool:

return"user"in request

def_process(self, request: dict) -> Optional[dict]:

        path = request.get("path", "")

        required = self.REQUIRED_PERMISSIONS.get(path)

if required:

print(f"  🔑 路径 {path} 需要权限: {required}")

            request["permission"] = required

return request

classRateLimitHandler(Handler):

"""速率限制处理器"""

def__init__(self, max_requests: int = 100):

super().__init__()

self.max_requests = max_requests

self._request_count = 0

def_can_handle(self, request: dict) -> bool:

return"permission"in request

def_process(self, request: dict) -> Optional[dict]:

self._request_count += 1

ifself._request_count > self.max_requests:

print(f"  ⏱ 速率限制触发!已超过 {self.max_requests} 次请求")

            request["rate_limited"] = True

return request

print(f"  ⏱ 请求计数: {self._request_count}/{self.max_requests}")

return request

classLoggingHandler(Handler):

"""日志处理器(放在链条最后)"""

def_process(self, request: dict) -> Optional[dict]:

        path = request.get("path", "/")

        status = "blocked"if request.get("rate_limited") else"allowed"

        user = request.get("user", "unknown")

print(f"  📝 日志: {user} 访问 {path} → {status}")

return request

# ===== 组装责任链 =====

chain = AuthHandler() \

    .set_next(PermissionHandler()) \

    .set_next(RateLimitHandler(max_requests=3)) \

    .set_next(LoggingHandler())

# 测试请求

response = chain.handle({

"path": "/api/orders",

"token": "invalid_token_xyz",

})

print(f"响应: {response}\n")

# 再来几次请求触发速率限制

for i inrange(4):

    chain.handle({"path": "/api/orders", "token": "valid_user123"})

```

---

## 五、实战重构:从一个"面条代码"到优雅架构

### 5.1 重构前:一团糟

```python

# utils/bad_analyzer.py — 这是一个典型的"什么都在一个文件里"的反面教材

import os

import json

import sqlite3

from datetime import datetime

defprocess_data(input_file, output_file, config_file="config.json"):

"""这个函数什么都干"""

# 读配置

withopen(config_file, "r") as f:

        config = json.load(f)

    threshold = config.get("anomaly_threshold", 0.5)

    db_path = config.get("database", "analysis.db")

# 读数据

withopen(input_file, "r") as f:

        data = json.load(f)

# 处理数据

    results = []

for item in data:

        score = 0

if item.get("value", 0) > threshold * 100:

            score += 0.3

if item.get("weight", 0) > 10:

            score += 0.2

if"flagged"in item.get("tags", []):

            score += 0.5

        result = {

"id": item["id"],

"score": score,

"timestamp": datetime.now().isoformat(),

        }

        results.append(result)

# 存结果

withopen(output_file, "w") as f:

        json.dump(results, f, indent=2)

# 写数据库

    conn = sqlite3.connect(db_path)

    cursor = conn.cursor()

    cursor.execute("""

        CREATE TABLE IF NOT EXISTS analysis_results (

            id TEXT PRIMARY KEY,

            score REAL,

            created_at TEXT

        )

    """)

for r in results:

        cursor.execute(

"INSERT OR REPLACE INTO analysis_results VALUES (?, ?, ?)",

            (r["id"], r["score"], r["timestamp"]),

        )

    conn.commit()

    conn.close()

return results

```

这个函数的毛病:**它同时做了至少五件事**——读配置、读数据、处理分析、写结果文件、写入数据库。任何一件事的变化都会影响其他部分。

### 5.2 重构后:各司其职

```python

# 第一步:配置加载器(单一职责)

classConfigLoader:

"""负责加载和解析配置"""

DEFAULTS = {

"anomaly_threshold": 0.5,

"database": "analysis.db",

"output_format": "json",

    }

def__init__(self, config_path: str = "config.json"):

self.config = dict(self.DEFAULTS)

if os.path.exists(config_path):

withopen(config_path, "r") as f:

self.config.update(json.load(f))

@property

defthreshold(self) -> float:

returnself.config["anomaly_threshold"]

@property

defdb_path(self) -> str:

returnself.config["database"]

# 第二步:数据加载器

classDataLoader:

"""负责从各种源加载数据"""

@staticmethod

defload_json(filepath: str) -> list[dict]:

withopen(filepath, "r", encoding="utf-8") as f:

return json.load(f)

# 第三步:评分引擎(核心算法)

classScoringEngine:

"""计算每项的异常分数"""

def__init__(self, threshold: float):

self.threshold = threshold

defscore_item(self, item: dict) -> dict:

"""给单个物品打分"""

        score = 0.0

if item.get("value", 0) > self.threshold * 100:

            score += 0.3

if item.get("weight", 0) > 10:

            score += 0.2

if"flagged"in item.get("tags", []):

            score += 0.5

return {

"id": item["id"],

"score": round(score, 2),

"timestamp": datetime.now().isoformat(),

        }

# 第四步:结果存储器

classResultStore:

"""存储分析结果"""

def__init__(self, db_path: str):

self.db_path = db_path

self._init_db()

def_init_db(self):

import sqlite3

        conn = sqlite3.connect(self.db_path)

        conn.execute("""

            CREATE TABLE IF NOT EXISTS analysis_results (

                id TEXT PRIMARY KEY,

                score REAL,

                created_at TEXT

            )

        """)

        conn.commit()

        conn.close()

defsave_to_file(self, results: list[dict], filepath: str):

withopen(filepath, "w", encoding="utf-8") as f:

            json.dump(results, f, ensure_ascii=False, indent=2)

defsave_to_db(self, results: list[dict]):

import sqlite3

        conn = sqlite3.connect(self.db_path)

for r in results:

            conn.execute(

"INSERT OR REPLACE INTO analysis_results VALUES (?, ?, ?)",

                (r["id"], r["score"], r["timestamp"]),

            )

        conn.commit()

        conn.close()

# 第五步:编排器——把各部分组装起来

classDataAnalyzer:

"""数据分析师——协调各个组件,不做任何具体工作"""

def__init__(self, config_path: str = "config.json"):

self.config = ConfigLoader(config_path)

self.scorer = ScoringEngine(self.config.threshold)

self.store = ResultStore(self.config.db_path)

defanalyze(self, input_file: str, output_file: str) -> list[dict]:

"""分析入口"""

        data = DataLoader.load_json(input_file)

        results = [self.scorer.score_item(item) for item in data]

self.store.save_to_file(results, output_file)

self.store.save_to_db(results)

return results

```

**重构的收益:**

- 每个类只有**一个存在的理由**(单一职责原则)

- 想换数据存储方式?只需改 `ResultStore`,不动其他代码

- 想改评分规则?只需改 `ScoringEngine`

- 想用不同配置格式?只需换 `ConfigLoader`

- 测试变得超简单:每个类都可以单独单元测试

---

## 六、实操练习

### 练习题

**第 1 题:实现一个简单的缓存装饰器**

基于第 2.1 节学到的装饰器知识,编写一个带 TTL(过期时间)的 LRU 缓存装饰器:

```python

import time

import functools

from typing import Any, Callable

deflru_cache_with_ttl(maxsize: int = 128, ttl_seconds: int = 300):

"""带过期时间的 LRU 缓存装饰器"""

defdecorator(func: Callable) -> Callable:

        cache = {}

@functools.wraps(func)

defwrapper(*args, **kwargs):

            key = (args, tuple(sorted(kwargs.items())))

if key in cache:

                value, expire_at = cache[key]

if time.time() < expire_at:

return value

            result = func(*args, **kwargs)

            expire_at = time.time() + ttl_seconds

iflen(cache) >= maxsize:

                oldest_key = min(cache, key=lambdak: cache[k][1])

del cache[oldest_key]

            cache[key] = (result, expire_at)

return result

return wrapper

return decorator

@lru_cache_with_ttl(maxsize=64, ttl_seconds=60)

deffetch_weather(city: str) -> dict:

print(f"  🌤 正在查询城市: {city}(模拟 API 调用)")

return {"city": city, "temp": 22, "humidity": 65}

# 第一次:调用 API

result1 = fetch_weather("北京")

# 第二次:命中缓存

result2 = fetch_weather("北京")

print(result1)

print(result2)

```

**第 2 题:工厂模式扩展——多数据库支持**

使用第 2.2 节的工厂模式思路,设计一个支持 SQLite、PostgreSQL 两种数据库连接器的工厂类:

```python

from abc importABC, abstractmethod

classDatabase(ABC):

@abstractmethod

defconnect(self, uri: str):

pass

@abstractmethod

defquery(self, sql: str) -> list:

pass

@abstractmethod

defclose(self):

pass

classSQLiteDB(Database):

defconnect(self, uri: str):

import sqlite3

self.conn = sqlite3.connect(uri.replace("sqlite:///", ""))

returnself

defquery(self, sql: str) -> list:

returnself.conn.execute(sql).fetchall()

defclose(self):

self.conn.close()

classDatabaseFactory:

    _databases = {

"sqlite": SQLiteDB,

"postgresql": None,  # 需要你补全

    }

@classmethod

defcreate(cls, database_type: str) -> Database:

if database_type notincls._databases:

raiseValueError(f"不支持的数据库类型: {database_type}")

returncls._databases[database_type]()

# 使用

db = DatabaseFactory.create("sqlite")

db.connect("sqlite:///test.db")

results = db.query("SELECT 1 + 1")

db.close()

```

**第 3 题:用策略模式实现多种排序算法**

```python

from abc importABC, abstractmethod

import time

import random

classSortStrategy(ABC):

@abstractmethod

defsort(self, data: list) -> list:

pass

@abstractmethod

defcomplexity(self) -> str:

pass

classBubbleSortStrategy(SortStrategy):

defsort(self, data: list) -> list:

        arr = data.copy()

        n = len(arr)

for i inrange(n):

for j inrange(0, n - i - 1):

if arr[j] > arr[j + 1]:

                    arr[j], arr[j + 1] = arr[j + 1], arr[j]

return arr

defcomplexity(self) -> str:

return"O(n^2)"

classQuickSortStrategy(SortStrategy):

defsort(self, data: list) -> list:

iflen(data) <= 1:

return data

        pivot = data[len(data) // 2]

        left = [x for x in data if x < pivot]

        middle = [x for x in data if x == pivot]

        right = [x for x in data if x > pivot]

returnself.sort(left) + middle + self.sort(right)

defcomplexity(self) -> str:

return"O(n log n) 平均"

classSorter:

def__init__(self, strategy: SortStrategy):

self.strategy = strategy

defset_strategy(self, strategy: SortStrategy):

self.strategy = strategy

defsort(self, data: list) -> list:

returnself.strategy.sort(data)

# 对比两种策略

data = [random.randint(1, 10000) for _ inrange(10000)]

for name, strategy in [("冒泡", BubbleSortStrategy()), ("快速", QuickSortStrategy())]:

    sorter = Sorter(strategy)

    start = time.perf_counter()

    result = sorter.sort(data)

    elapsed = time.perf_counter() - start

print(f"{name}排序({strategy.complexity()}): {elapsed:.4f}s")

```

**第 4 题:组合模式实现文件系统遍历**

组合模式允许你把"单个对象"和"对象集合"统一对待。请实现一个简单的文件系统操作类:

```python

from abc importABC, abstractmethod

classFileSystemComponent(ABC):

@abstractmethod

defshow_details(self, indent: str = "") -> str:

pass

@abstractmethod

defget_size(self) -> int:

pass

classFile(FileSystemComponent):

def__init__(self, name: str, size: int):

self.name = name

self.size = size

defshow_details(self, indent: str = "") -> str:

returnf"{indent}📄 {self.name} ({self.size} bytes)"

defget_size(self) -> int:

returnself.size

classDirectory(FileSystemComponent):

def__init__(self, name: str):

self.name = name

self._children: list[FileSystemComponent] = []

defadd(self, component: FileSystemComponent):

self._children.append(component)

defshow_details(self, indent: str = "") -> str:

        lines = [f"{indent}📁 {self.name}/"]

for child inself._children:

            lines.append(child.show_details(indent + "  "))

return"\n".join(lines)

defget_size(self) -> int:

returnsum(child.get_size() for child inself._children)

# 使用

root = Directory("项目")

src = Directory("src")

src.add(File("main.py", 2048))

src.add(File("utils.py", 1024))

root.add(src)

root.add(File(".gitignore", 128))

print(root.show_details())

print(f"\n总大小: {root.get_size()} bytes")

```

**第 5 题:综合实战——用多个模式搭建一个插件系统**

这是一个综合性题目。请用以下模式设计一个可扩展的插件系统:

-**工厂模式**:创建不同类型的插件

-**策略模式**:每个插件是一个策略

-**观察者模式**:插件可以向事件总线注册回调

-**外观模式**:提供一个简单的 `PluginManager` 类

提示框架:

```python

from abc importABC, abstractmethod

from typing import Callable, Any

classPlugin(ABC):

"""插件基类"""

@abstractmethod

defexecute(self, context: dict) -> dict:

pass

@abstractmethod

defname(self) -> str:

pass

classEventNotifier:

"""事件通知器"""

def__init__(self):

self._handlers: dict[str, list[Callable]] = {}

defon(self, event: str, handler: Callable):

self._handlers.setdefault(event, []).append(handler)

deftrigger(self, event: str, data: dict = None):

for handler inself._handlers.get(event, []):

            handler(event, data or {})

classPluginManager:

"""插件管理器——外观模式"""

def__init__(self):

self.notifier = EventNotifier()

self._plugins: dict[str, Plugin] = {}

defregister_plugin(self, plugin: Plugin):

self._plugins[plugin.name()] = plugin

print(f"  ✅ 插件 '{plugin.name()}' 已注册")

defexecute(self, plugin_name: str, context: dict) -> dict:

if plugin_name notinself._plugins:

raiseValueError(f"未找到插件: {plugin_name}")

returnself._plugins[plugin_name].execute(context)

deflist_plugins(self) -> list[str]:

returnlist(self._plugins.keys())

# 定义插件

classEmailPlugin(Plugin):

defname(self) -> str:

return"email"

defexecute(self, context: dict) -> dict:

        recipient = context.get("recipient", "admin@example.com")

print(f"  ✉ 发送邮件至: {recipient}")

return {"status": "sent", "to": recipient}

classSMSPlugin(Plugin):

defname(self) -> str:

return"sms"

defexecute(self, context: dict) -> dict:

        phone = context.get("phone", "+8613800138000")

print(f"  📱 发送短信至: {phone}")

return {"status": "delivered", "phone": phone}

# 运行

manager = PluginManager()

manager.register_plugin(EmailPlugin())

manager.register_plugin(SMSPlugin())

print(f"已安装插件: {', '.join(manager.list_plugins())}")

manager.execute("email", {"recipient": "user@example.com"})

```

---

## 七、设计模式的"反模式"

在结束之前,我要说一些很重要但不太受欢迎的话:**不是所有地方都需要设计模式。**

### 7.1 常见陷阱

| 陷阱 | 表现 | 建议 |

|------|------|------|

| **过度设计** | 一个小脚本里用了 8 个模式 | 用小脚本就该有小脚本的写法 |

| **为了模式而模式** | 明明 5 行代码能解决,非要拆成 5 个类 | 先满足功能,再考虑扩展性 |

| **滥用继承** | 三层以上的继承树 | Python 优先用组合(composition),少用继承 |

| **忽视 Pythonic** | 用 Java 的思维写 Python | Python 有 Python 的习惯用法 |

### 7.2 SOLID 原则速记

设计模式背后有一组更底层的原则,叫做 SOLID:

-**S**ingle Responsibility(单一职责):一个类只做一件事

-**O**pen/Closed(开闭原则):对扩展开放,对修改关闭

-**L**iskov Substitution(里氏替换):子类应该能透明替换父类

-**I**nterface Segregation(接口隔离):接口尽量小

-**D**ependency Inversion(依赖倒置):高层和低层都应依赖抽象

记住:**模式是手段,SOLID 是目的。** 真正重要的不是你用了几个模式,而是你的代码是否易读、易维护、易测试。

---

## 八、总结

这一期的内容比较多,我们系统地学习了:

1.**单例模式**——保证全局唯一实例。但在 Python 中,优先考虑模块级变量。

2.**工厂模式**——解耦对象的创建和使用,便于扩展。

3.**建造者模式**——链式调用构建复杂对象,替代"参数爆炸"。

4.**适配器模式**——让不同接口的组件可以协同工作。

5.**装饰器模式**——给对象添加新功能,与 Python 的 `@decorator` 相辅相成。

6.**外观模式**——简化复杂子系统的一行调用。

7.**策略模式**——运行时切换算法,符合开闭原则。

8.**观察者模式**——事件驱动架构的基础,解耦发布者和订阅者。

9.**责任链模式**——请求逐层处理,每层职责独立。

10.**重构实战**——把一个"面条函数"拆成职责清晰的多个类。

记住最重要的一句话:**好的代码不是用了多少模式,而是让读代码的人一眼就能看懂你在做什么。**

---

## 九、下期预告

**Episode 23:Python 异步编程深度进阶——asyncio 高级用法与实战**

我们已经在前面的章节(Episode 13)介绍了异步编程的基础概念。这一期我们将深入进阶:

- asyncio 事件循环的内部原理

- Task、Future、Semaphore、Queue 的高级用法

- 异步上下文管理器与异步生成器

- 异步 WebSocket 实战

- 如何将现有同步代码安全地异步化

- 常见陷阱:死锁、竞态条件、阻塞调用

敬请期待!

---

**📚 系列回顾**

到目前为止我们学过的内容:

| Episode | 主题 |

|---------|------|

| 01 | 环境搭建与基础语法 |

| 02 | 数据结构与字符串处理 |

| 03 | 面向对象编程 |

| 04 | 装饰器、生成器与文件 IO |

| 05 | 爬虫入门 |

| 06 | 数据分析入门 |

| 07 | Web 后端开发 |

| 08 | AI 实战入门 |

| 09 | 前端入门 |

| 10 | 项目实战——Dashboard |

| 11 | Docker 容器化部署 |

| 12 | 数据库进阶 |

| 13 | 异步编程与并发 |

| 14 | 日志系统与调试技巧 |

| 15 | 自动化运维与脚本实战 |

| 16 | 单元测试与代码质量 |

| 17 | 微服务入门 |

| 18 | CI/CD 与 DevOps 实战 |

| 19 | 并发与多线程深度实践 |

| 20 | 包管理与发布 |

| 21 | 性能优化全指南 |

| 22 | 设计模式实战 ← **本期** |

恭喜你!从零基础到现在,你已经掌握了一套完整的 Python 工程技能链。设计模式是你走向"资深工程师"的关键一步——它教你如何组织代码的结构,而不仅仅是"怎么写对的逻辑"。接下来就看你想拿这套技能去做什么了!💪

最新文章

随机文章