作者: andylin02
学习章节: 第1章 欢迎来到设计模式世界:设计模式入门——策略模式
关键词:策略模式, Python, 设计原则, 封装变化, 多用组合少用继承, 鸭子类型, abc模块

策略模式Python深度解析:用鸭子类型优雅应对变化

大家好,我是 andylin02。在上一篇《Head First设计模式》Python版概览中,我们梳理了全书的核心脉络,知道设计模式是应对软件变化的利器,并快速预览了策略模式如何拯救Joe的鸭子模拟器。今天,我们将正式进入第1章,用Python的方式重新演绎这个经典故事,并深入理解它背后的设计原则。


一、开篇复习:我们为什么需要设计模式?

回顾上一篇的内容,我们强调了一个残酷的现实:软件中的需求总是在变。如果代码结构是硬编码的、铁板一块,一次微小改动就可能引发连锁崩溃。《Head First设计模式》则教你把变化的部分“关进笼子里”。而这一切的起点,就是第1章策略模式。它不只是一个具体模式,更是你理解“封装变化”、“多用组合、少用继承”、“面向接口编程”三大设计原则的最佳敲门砖。

今天,我们用Python的视角重走Joe的“鸭子困境”,你会惊讶地发现:Python的灵活性让设计模式更简洁,但背后的设计思想完全一致。

二、故事的开始:鸭子模拟器的灾难

Joe的游戏公司开发了一款模拟池塘的游戏,里面有各种鸭子:绿头鸭、红头鸭、橡皮鸭、诱饵鸭……它们都会游泳、会叫,但外观各不相同。Joe用经典的继承设计了 Duck 基类:

class Duck:
    def quack(self):
        print("Quack! Quack!")

    def swim(self):
        print("All ducks can swim.")

    def display(self):
        raise NotImplementedError

一切完美,直到产品经理说:“让鸭子会飞。”

Joe毫不犹豫地在 Duck 里加了 fly() 方法。结果,橡皮鸭、诱饵鸭也继承了飞行能力,橡皮鸭在屏幕上扑腾着飞了起来——这显然不对。Joe赶紧在 RubberDuck 里覆盖 fly() 为空,又修改 quack() 为“吱吱叫”……很快,行为代码在子类间疯狂重复,每次新增鸭子类型都胆战心惊。

问题根因:继承将行为静态绑定在类上,无法灵活应对变化。

三、接口?也不是银弹

Joe想到用接口(Python里可以用抽象基类)来定义 FlyableQuackable,让会飞的鸭子实现它们:

from abc import ABC, abstractmethod

class Flyable(ABC):
    @abstractmethod
    def fly(self):
        pass

class Quackable(ABC):
    @abstractmethod
    def quack(self):
        pass

但这导致新问题:所有会飞的鸭子都要实现完全相同的 fly() 方法,代码重复。接口抽象了能力,却没有提供代码复用的机制。

四、设计原则拯救世界

面对两难处境,书中给出了第一个设计原则:

找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

在鸭子模拟器中,“飞行行为”和“叫声行为”正是变化的部分,应该抽离成独立的类族。

接着第二个原则:

针对接口编程,而不是针对实现。

这里的“接口”是广义的,在Python中可以是抽象基类,也可以直接依赖“鸭子类型”——只要对象有 fly() 方法,就能当飞行行为用。不过为了结构清晰,我们仍用抽象基类来明确定义契约。

第三个原则:

多用组合,少用继承。

每个鸭子类不再通过继承获得飞行能力,而是组合一个飞行行为对象和一个叫声行为对象。这样可以在运行时动态切换行为,复用代码也更灵活。

五、策略模式正式登场

这三个原则汇聚起来,就形成了策略模式

定义:策略模式定义了算法族,分别封装起来,让它们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。

在鸭子例子中,“算法族”就是飞行行为和叫声行为。我们将其封装成独立的类,可随时替换,鸭子(客户)并不关心内部实现。

UML类图

has a

has a

«abstract»

Duck

+fly_behavior : FlyBehavior

+quack_behavior : QuackBehavior

+perform_fly() : void

+perform_quack() : void

+swim() : void

+display() : void

+set_fly_behavior(FlyBehavior) : void

+set_quack_behavior(QuackBehavior) : void

MallardDuck

+display() : void

RedheadDuck

+display() : void

RubberDuck

+display() : void

«abstract»

FlyBehavior

+fly() : void

FlyWithWings

+fly() : void

FlyNoWay

+fly() : void

FlyRocketPowered

+fly() : void

«abstract»

QuackBehavior

+quack() : void

Quack

+quack() : void

Squeak

+quack() : void

MuteQuack

+quack() : void

六、Python完整源码

我们用Python的 abc 模块定义抽象行为,借助组合实现策略模式。

from abc import ABC, abstractmethod

# ========== 飞行行为抽象基类及实现 ==========
class FlyBehavior(ABC):
    @abstractmethod
    def fly(self):
        pass

class FlyWithWings(FlyBehavior):
    def fly(self):
        print("I'm flying with wings!")

class FlyNoWay(FlyBehavior):
    def fly(self):
        print("I can't fly.")

class FlyRocketPowered(FlyBehavior):
    def fly(self):
        print("I'm flying with a rocket!")

# ========== 叫声行为抽象基类及实现 ==========
class QuackBehavior(ABC):
    @abstractmethod
    def quack(self):
        pass

class Quack(QuackBehavior):
    def quack(self):
        print("Quack! Quack!")

class Squeak(QuackBehavior):
    def quack(self):
        print("Squeak! Squeak!")

class MuteQuack(QuackBehavior):
    def quack(self):
        print("<< Silence >>")

# ========== 鸭子抽象基类 ==========
class Duck(ABC):
    def __init__(self):
        # 行为对象由子类负责初始化
        self.fly_behavior = None
        self.quack_behavior = None

    def perform_fly(self):
        self.fly_behavior.fly()   # 委托给行为对象

    def perform_quack(self):
        self.quack_behavior.quack()

    @staticmethod
    def swim():
        print("All ducks float, even decoys!")

    @abstractmethod
    def display(self):
        pass

    # 动态设置行为,支持运行时切换
    def set_fly_behavior(self, fb: FlyBehavior):
        self.fly_behavior = fb

    def set_quack_behavior(self, qb: QuackBehavior):
        self.quack_behavior = qb

# ========== 具体鸭子子类 ==========
class MallardDuck(Duck):
    def __init__(self):
        super().__init__()
        self.fly_behavior = FlyWithWings()
        self.quack_behavior = Quack()

    def display(self):
        print("I'm a real Mallard duck")

class RedheadDuck(Duck):
    def __init__(self):
        super().__init__()
        self.fly_behavior = FlyWithWings()
        self.quack_behavior = Quack()

    def display(self):
        print("I'm a redhead duck")

class RubberDuck(Duck):
    def __init__(self):
        super().__init__()
        self.fly_behavior = FlyNoWay()
        self.quack_behavior = Squeak()

    def display(self):
        print("I'm a rubber duckie")

class DecoyDuck(Duck):
    def __init__(self):
        super().__init__()
        self.fly_behavior = FlyNoWay()
        self.quack_behavior = MuteQuack()

    def display(self):
        print("I'm a decoy duck")

# ========== 测试 ==========
if __name__ == "__main__":
    mallard = MallardDuck()
    mallard.display()
    mallard.perform_quack()
    mallard.perform_fly()

    print("-----------------")

    rubber = RubberDuck()
    rubber.display()
    rubber.perform_quack()
    rubber.perform_fly()

    # 动态改变行为——策略模式的真正威力
    print("\n>>> 给橡皮鸭安装火箭引擎!")
    rubber.set_fly_behavior(FlyRocketPowered())
    rubber.perform_fly()

运行结果:

I'm a real Mallard duck
Quack! Quack!
I'm flying with wings!
-----------------
I'm a rubber duckie
Squeak! Squeak!
I can't fly.

>>> 给橡皮鸭安装火箭引擎!
I'm flying with a rocket!

橡皮鸭原本不会飞,通过 set_fly_behavior 动态装备了火箭飞行能力。这正是策略模式运行时扩展的魅力。

七、深度思考:策略模式如何体现设计原则?

设计原则 策略模式中的体现
封装变化 将飞行、叫声行为抽离为独立类族,与不变的 swim()display() 分离。
面向接口编程 Duck 依赖 FlyBehaviorQuackBehavior 抽象,不依赖具体实现。
多用组合,少用继承 每个鸭子对象组合了行为组件,而非硬编码继承。
开闭原则(后续章节) 新增飞行方式(如火箭飞行)只需新增一个类,无需修改原有代码。

这种设计让系统弹性大增,你能在不触碰现有代码的前提下扩展新行为,完美应对需求变化。

八、Python特色:更轻量级的策略——函数就是策略

Python的函数是一等公民,这让我们可以直接用函数作为策略,省去创建类的麻烦。比如:

from typing import Callable

class Duck:
    def __init__(self, fly_fn: Callable, quack_fn: Callable):
        self._fly = fly_fn
        self._quack = quack_fn

    def perform_fly(self):
        self._fly()

    def perform_quack(self):
        self._quack()

# 策略函数
def fly_with_wings():
    print("Flying with wings!")

def fly_no_way():
    print("Can't fly.")

def quack_loud():
    print("QUACK!")

# 使用
mallard = Duck(fly_with_wings, quack_loud)
mallard.perform_fly()

这种“函数式策略”在轻量场景下非常Pythonic,但缺点是失去了类型契约的显式约束。如果策略需要复杂状态或多种方法,类依然是更好的选择。

九、策略模式 vs 其他相似设计模式

学习策略模式时,它很容易与状态模式命令模式混淆。我们做一个前瞻性对比:

1. 策略模式 vs 状态模式

  • 相同点:都通过组合不同行为对象,并在运行时切换它们来改变行为。
  • 不同点
    • 意图:策略是让客户端主动选择不同的算法;状态是对象内部状态改变时自动切换行为。
    • 客户端感知:策略模式中,客户端明确知道并选择策略;状态模式中,状态切换对客户端透明。
  • 类比:策略像导航软件的“最快/最短/躲避拥堵”路径偏好;状态像自动洗衣机的“进水→洗涤→漂洗→脱水”程序。

2. 策略模式 vs 命令模式

  • 相同点:都可能把“动作”封装成对象。
  • 不同点
    • 命令模式关注请求的封装,解耦调用者与执行者,常支持撤销、队列;策略模式关注算法的互换,描述做一件事的不同方式。
    • 命令通常是一次性或需要被记录的操作;策略则一般是持久附加在对象上的行为配置。
  • 类比:命令像遥控器上的“音量+”按钮;策略像汽车可选的“经济/运动”驾驶模式。

这些对比能帮助你在后续章节抓住模式的本质区别。

十、本章读书笔记与总结

核心知识点清单

  • 继承虽然提供复用,但容易将行为静态绑定,需求变化时导致代码脆弱。
  • 单纯使用接口能抽离行为,但无法复用实现,产生大量重复代码。
  • 设计原则1:找出变化的部分并封装之。
  • 设计原则2:面向接口编程,而非面向实现。
  • 设计原则3:多用组合,少用继承。
  • 策略模式定义:定义一系列算法,封装每一个算法,并使它们可以相互替换。策略模式让算法的变化独立于使用它的客户。
  • 结构:抽象策略接口 → 具体策略实现 → 上下文持有策略引用,通过委托执行行为。
  • Python中可用 abc 抽象基类实现,或更轻量地直接使用函数。
  • 可在运行时动态切换策略,提供极高的灵活度。
  • 适用场景:多个类只在行为上有区别;需要不同算法变体;算法使用了客户端不应知道的复杂数据;类中定义了多种行为,以大量条件语句实现。

一张图牢记策略模式

委托

客户端

上下文 Duck

策略接口 FlyBehavior

具体策略 FlyWithWings

具体策略 FlyNoWay

具体策略 FlyRocketPowered

客户端只需指定策略,上下文将实际工作委托给策略对象。

十一、下篇预告:第2章 · 观察者模式(Python版)

今天我们通过鸭子的故事,用Python实现了策略模式,并深刻理解了“封装变化”、“面向接口编程”、“多用组合”三大原则。下一章,我们将进入观察者模式:想象你订阅了一份报纸,一旦有新刊出版,报社就会自动送上门来——这就是观察者模式的核心。我们将用Python构建一个气象站数据推送系统,并对比推模型和拉模型,还会看看如何用Python的 Observable 简化实现。敬请期待!


本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐