下一讲 — 第七模块:设计模式
GoF经典模式应用于书店:工厂方法、单例、观察者、策略与装饰器,每种模式附SysML v2映射。
定义类必须履行的正式契约——抽象基类、Protocol、契约式设计与SysML v2 interface def。
接口定义了类能做什么,而不指定怎么做。依赖接口的代码与任何特定实现解耦:
| 机制 | 子类型模型 | 工作方式 |
|---|---|---|
| ABC | 名义子类型 | 类必须显式继承ABC并实现所有抽象方法。缺少任何方法时Python在实例化时抛出TypeError。 |
| Protocol | 结构子类型 | 任何拥有所需方法的类都兼容,无论继承关系如何。无需显式声明。 |
为书店领域定义三个精简接口,每个捕获单一职责:
from abc import ABC, abstractmethod
from typing import TypeVar, Type
T = TypeVar("T")
class Payable(ABC):
"""可以支付和退款的任何事物。"""
@abstractmethod
def process(self) -> bool: ...
@abstractmethod
def refund(self) -> bool: ...
class Discountable(ABC):
"""可以应用折扣的任何事物。"""
@abstractmethod
def apply_discount(self, percentage: float) -> None: ...
@abstractmethod
def get_discount_rate(self) -> float: ...
class Serialisable(ABC):
"""可以序列化为/从字典的任何事物。"""
@abstractmethod
def to_dict(self) -> dict: ...
@classmethod
@abstractmethod
def from_dict(cls: Type[T], data: dict) -> T: ...一个类可以同时实现多个ABC(Python多重继承):
# Book同时实现Discountable和Serialisable
class Book(ABC, Discountable, Serialisable):
def apply_discount(self, percentage: float) -> None:
if not 0 <= percentage <= 100:
raise ValueError(f"折扣必须在0-100之间,得到{percentage}")
self._price = round(self._price * (1 - percentage/100), 2)
def get_discount_rate(self) -> float: return self._discount
def to_dict(self) -> dict:
return {"isbn":self.isbn,"title":self.title,"price":self._price}
# Payment实现Payable
class Payment(Payable):
def process(self) -> bool: self._status="complete"; return True
def refund(self) -> bool:
if self._status!="complete": raise RuntimeError("未完成付款,无法退款")
self._status="refunded"; return True任何拥有正确方法的类都满足Protocol——不需要继承。非常适合第三方类型和测试替身:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Priceable(Protocol):
"""拥有可折扣价格的任何事物。"""
@property
def price(self) -> float: ...
def apply_discount(self, percentage: float) -> None: ...
# GiftCard满足Priceable,无需继承它
class GiftCard:
def __init__(self, code, value):
self.code=code; self._price=value
@property
def price(self): return self._price
def apply_discount(self, pct):
self._price = round(self._price * (1 - pct/100), 2)
def apply_sale_price(item: Priceable, pct: float) -> None:
item.apply_discount(pct); print(f" 新价格:£{item.price:.2f}")
book = DigitalBook("978","清洁代码","Martin",29.99,"PDF",5.2,"...")
card = GiftCard("GC-001", 25.00)
apply_sale_price(book, 10) # 可用——Book有price和apply_discount
apply_sale_price(card, 10) # 可用——GiftCard结构上满足Priceable
print(isinstance(card, Priceable)) # True(因为@runtime_checkable)契约式设计将前置条件(方法期望什么)、后置条件(方法保证什么)和不变量(始终成立的条件)形式化:
class Book(ABC, Discountable, Serialisable):
"""
类不变量:isbn为非空字符串,_price >= 0,0 <= _discount <= 100
"""
def apply_discount(self, percentage: float) -> None:
"""
前置条件:0 <= percentage <= 100
后置条件:self.price == old_price * (1 - percentage/100)
"""
if not 0 <= percentage <= 100:
raise ValueError(f"折扣必须在0-100之间,得到{percentage}")
old_price = self._price
self._discount = percentage
self._price = round(old_price * (1 - percentage/100), 2)
assert self._price >= 0, "价格不变量被违反" # 后置条件interface def Payable {
action def process : Boolean;
action def refund : Boolean;
}
interface def Discountable {
action def apply_discount { in percentage : Real; }
}
// Book同时实现多个接口
abstract part def Book :> Discountable, Serialisable {
attribute isbn : String; attribute price : Real;
abstract action def get_details : String;
}interface def + abstract actionsclass X(InterfaceA, InterfaceB)part def X :> InterfaceA, InterfaceBrequire约束ensure约束将规范(做什么)与实现(怎么做)分离。代码依赖接口,而非具体类。
需要显式继承。Python在实例化时强制完整性。适合受控代码库。
拥有正确方法即可满足,无需继承。适合外部类型和测试替身。
class X(ABC_A, ABC_B)支持ISP——小而专注的契约优于一个大接口。
前置条件(if/raise)、后置条件(assert)、不变量使契约显式可检查。
interface def + part def X :> Interface直接映射Python ABC。
GoF经典模式应用于书店:工厂方法、单例、观察者、策略与装饰器,每种模式附SysML v2映射。