Ran Wei /面向对象设计/第七模块
EN
面向对象设计 — Ran Wei

第七模块:设计模式

工厂方法、单例、观察者、策略——GoF经典模式应用于书店领域,附SysML v2映射。

预备知识: OOD 第六模块Python 3UML 2.5SysML v2
1

什么是设计模式?

🏠 生活类比
设计模式就像建筑行业的标准解决方案:面对"如何建造楼梯"或"如何设计通风系统"这类反复出现的问题,有经过验证的成熟方案。建筑师不需要每次从零发明,而是选择适合情境的解决方案。

设计模式是针对反复出现设计问题的可复用命名解决方案,由"四人帮"(1994年)整理为三类:

类别意图本模块示例
创建型如何创建对象工厂方法、单例
结构型如何组合对象装饰器
行为型对象如何交互观察者、策略
ℹ️ 模式不是复制粘贴
模式是解决一类问题的模板,不是现成代码片段。应用模式需要理解它解决的问题。过度使用模式会增加不必要的复杂性。
2

工厂方法

问题:创建Book对象的代码与具体类型耦合。添加AudioBook需要找到所有DigitalBook(...)调用点。

解决方案:工厂集中管理创建。调用者依赖Book接口,永不依赖具体类:

Python
class BookFactory:
    @staticmethod
    def create(data: dict) -> Book:
        t = data.get("type","").lower()
        if t == "digital":
            return DigitalBook(
                isbn=data["isbn"], title=data["title"],
                author=data["author"], price=data["price"],
                file_format=data["file_format"],
                file_size=data["file_size"],
                download_url=data["download_url"],
            )
        elif t == "physical":
            return PhysicalBook(
                isbn=data["isbn"], title=data["title"],
                author=data["author"], price=data["price"],
                weight=data["weight"],
                dimensions=data["dimensions"],
                warehouse=data["warehouse"],
            )
        else:
            raise ValueError(f"未知图书类型: {t!r}")

# 调用者使用Book接口——不了解具体类型
books: list[Book] = [BookFactory.create(d) for d in raw_data]
for b in books: print(b.get_details())  # 多态自动处理
diagram
图1 — BookFactory创建正确的具体类型。Inventory使用单例模式——每次调用get_instance()都返回同一实例。
3

单例

问题:多个Inventory实例导致库存数据不一致。解决方案:确保最多存在一个实例(线程安全):

Python
import threading

class Inventory:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        with cls._lock:
            if cls._instance is None:
                cls._instance = super().__new__(cls)
                cls._instance._catalogue = {}
                cls._instance._observers = []
        return cls._instance

    def add_book(self, book: Book):
        self._catalogue[book.isbn] = book

    def update_stock(self, isbn: str, delta: int):
        book = self.get_book(isbn)
        book.stock = max(0, book.stock + delta)
        self._notify(isbn, book.stock)

# 两次调用返回相同对象
assert Inventory() is Inventory()  # True
⚠️ 注意
单例使单元测试更困难(测试间共享状态)。在生产代码中优先使用依赖注入;仅在真正需要全局一致性时使用单例。
4

观察者

问题:多个服务(邮件、仪表盘、补货)需要在库存变化时响应。从Inventory直接调用每个服务造成紧耦合。

解决方案:观察者模式。Inventory通知所有注册的观察者,无需知道它们做什么:

Python
from abc import ABC, abstractmethod

class StockObserver(ABC):
    @abstractmethod
    def on_stock_change(self, isbn: str, new_qty: int) -> None: ...

class EmailAlertService(StockObserver):
    def on_stock_change(self, isbn, new_qty):
        if new_qty == 0:
            print(f"邮件:{isbn}已缺货")
        elif new_qty < 5:
            print(f"邮件:库存预警——{isbn}:剩余{new_qty}本")

class DashboardService(StockObserver):
    def on_stock_change(self, isbn, new_qty):
        print(f"仪表盘:{isbn}库存更新为{new_qty}本")

# 观察者连接
inv = Inventory.get_instance()
inv.subscribe(EmailAlertService())
inv.subscribe(DashboardService())
inv.update_stock("978-1-11", -3)
# 邮件:库存预警——978-1-11:剩余2本
# 仪表盘:978-1-11库存更新为2本
diagram
图2 — 观察者:Inventory实现StockSubject并通知所有StockObserver。策略:PricingStrategy由StandardPricing和SeasonalPricing实现。
5

策略

问题:定价逻辑因情境而异(标准、季节性、会员)。在Order中硬编码if/elif链脆弱且违反OCP。

解决方案:策略模式。每种算法是独立的类,可在运行时替换:

Python
class PricingStrategy(ABC):
    @abstractmethod
    def calculate(self, base_price: float) -> float: ...

class StandardPricing(PricingStrategy):
    def calculate(self, base): return base

class SeasonalPricing(PricingStrategy):
    def __init__(self, factor): self._factor = factor
    def calculate(self, base): return round(base * self._factor, 2)

class Order:
    def __init__(self, oid, customer, strategy=None):
        self._strategy = strategy or StandardPricing()

    def total(self):
        raw = sum(i.subtotal() for i in self._items)
        return round(self._strategy.calculate(raw), 2)

    def set_pricing(self, s: PricingStrategy): self._strategy = s

# 运行时策略替换
order = Order("O-001", customer)
print(order.total())                    # £29.99——标准定价
order.set_pricing(SeasonalPricing(0.8))
print(order.total())                    # £23.99——季节性八折
💡 策略 vs 继承
可以通过子类化Order得到DiscountedOrder、MemberOrder等——但每条新定价规则增加一个类。策略保持类数量稳定:一个Order类,多种可互换的策略。这是OCP在算法上的应用。
6

SysML v2 对应与总结

模式SysML v2表达
工厂方法factory part def中的action def create;返回类型化的Book
单例multiplicity [1]的part def Inventory<<singleton>>构造型
观察者interface def StockObserver;Inventory向所有观察者发送流
策略interface def PricingStrategy;Order有ref strategy : PricingStrategy
工厂方法

集中管理创建。调用者依赖抽象类型。添加新类型只需修改工厂。

单例

唯一实例。全局一致状态。但:测试隔离更困难。

观察者

事件驱动。Subject不了解观察者。运行时添加/移除。

策略

用可互换算法对象替换if/elif链。算法的OCP应用。

通用原则

模式是命名解决方案,提供团队共同的设计语言。

选择时机

理解问题再用模式。过度使用增加不必要的复杂性。

📚
下一讲 — 第八模块:OOD实践

SOLID原则应用于完整领域、重构反模式、测试策略,以及完整的集成模型。