外观模式 vs 策略模式:完整对比与代码示例

外观模式(Facade)和策略模式(Strategy)都是非常经典的设计模式,但它们的设计初衷、要解决的问题以及代码结构完全不同。

一句话概括核心区别:外观模式是为了“简化复杂系统的使用”,而策略模式是为了“灵活切换算法实现”。

一、🎭 外观模式 (Facade) —— “一键启动电脑”

  • 目的:为子系统中的一组复杂接口,提供一个统一的、简化的高级接口
  • 解决什么问题复杂性。当一个子系统包含几十个类,调用流程极其繁琐时,外观模式提供一个“傻瓜式”入口,屏蔽内部细节。
  • 本质封装与聚合。把多个类的复杂调用,包装成一个简单的方法。
  • 结构:创建一个新的类(Facade),该类内部持有子系统的多个对象,对外暴露几个简单的方法。
  • 客户端感知:客户端只和 Facade 交互,不知道也不关心背后有多少个类在干活。
  • 典型场景:复杂的第三方库集成、框架的入口类(如 Django 的 Model.objects.get 背后封装了 SQL 查询构建、连接管理)。

二、🔄 策略模式 (Strategy) —— “出门选择交通工具”

  • 目的:定义一系列可互换的算法(或业务规则),让客户端在运行时动态选择使用哪一种。
  • 解决什么问题多变的算法。当同一个业务有不同的实现方式(如不同的排序算法、不同的支付渠道),避免在代码里写大量的 if-else
  • 本质委托与多态。将算法抽取成独立的类,让它们实现同一个接口,上下文(Context)只持有接口引用,运行时再注入具体实现。
  • 结构:策略接口(Strategy) + 多个具体策略类(ConcreteStrategy) + 上下文类(Context,负责持有并调用策略)。
  • 客户端感知:客户端必须知道有哪些具体策略,并主动选择/注入其中一个给 Context。
  • 典型场景:电商系统根据会员等级计算折扣;支付方式选择(微信/支付宝/银行卡);地图导航的路径规划(步行/驾车/公交)。

三、📊 核心区别对比表

对比维度外观模式 (Facade)策略模式 (Strategy)
核心意图简化复杂调用(统一入口)切换具体算法(行为独立)
关注点关注 “怎么把这些类拼起来完成一件事”关注 “同一件事的不同做法如何独立替换”
设计阶段通常用于架构层,应对系统臃肿通常用于业务层,应对规则变化
类/对象关系依赖关系:Facade 依赖于子系统组合关系:Context 组合 Strategy 接口
运行时行为静态的,客户端调用时 Facade 内部逻辑通常固定动态的,客户端可以在运行时 setStrategy(new X()) 切换
是否隐藏细节。彻底隐藏底层类,客户端一无所知。客户端必须清楚不同策略的含义才能选择
典型代码形态封装方法调用链接口 + 多个实现类 + 运行时赋值
符合的设计原则最少知识原则(只跟朋友说话)开闭原则(新增策略无需修改旧代码)

四、💡 举个直观的生活例子

假设你要去 “下单购买一件商品”

  • 如果使用外观模式:你对着手机喊“下单”。外观模式背后的操作是:检查库存 → 生成订单 → 扣减库存 → 发起支付 → 通知物流。你只管喊,背后所有流程自动跑完。
  • 如果使用策略模式:你对着手机说“用微信支付”或“用支付宝”。策略模式背后的操作是:无论选哪个,都执行 pay(amount) 方法,但具体实现(调用微信SDK还是支付宝SDK)完全隔离。你必须指定用哪个策略,但不用管具体怎么调接口。

五、🧪 Python 代码示例

🎭 外观模式 —— 家庭影院“一键观影”

场景:你想看一场电影,但需要依次执行:开投影仪 → 设HDMI输入 → 开音响 → 调音量 → 关灯。这一套流程很繁琐。外观模式就是给你提供一个 “一键观影” 按钮。

# 1. 复杂的子系统 (Subsystems)
class Projector:
    def on(self): print("🎬 投影仪已开启")
    def off(self): print("🎬 投影仪已关闭")
    def set_input(self, source): print(f"📡 信号源切换至: {source}")

class SoundSystem:
    def on(self): print("🔊 音响已开启")
    def off(self): print("🔊 音响已关闭")
    def set_volume(self, level): print(f"🔊 音量调节至: {level}")

class Lights:
    def dim(self, level): print(f"💡 灯光调暗至: {level}%")
    def on(self): print("💡 灯光已全亮")

# 2. 外观类 (Facade) —— 提供统一入口
class HomeTheaterFacade:
    def __init__(self, projector, sound, lights):
        self.projector = projector
        self.sound = sound
        self.lights = lights

    def watch_movie(self, movie):
        print("\n=== 🎥 开始电影模式 ===")
        self.lights.dim(20)
        self.projector.on()
        self.projector.set_input("HDMI 1")
        self.sound.on()
        self.sound.set_volume(50)
        print(f"▶️ 正在播放: {movie}")

    def end_movie(self):
        print("\n=== 🎬 结束观影 ===")
        self.sound.off()
        self.projector.off()
        self.lights.on()

# 3. 客户端调用 (Client)
if __name__ == "__main__":
    facade = HomeTheaterFacade(Projector(), SoundSystem(), Lights())
    facade.watch_movie("《阿凡达》")
    facade.end_movie()

✅ 外观模式的效果:客户端面对的是 watch_movie 这一个简单的方法。即使背后有几十个类,客户端也毫无感知,复杂性被彻底封装了。

🔄 策略模式 —— 商城的动态折扣计算

场景:双11购物车结算,折扣规则很多(新人、会员、满减)。客户端必须明确告诉系统“今天该用什么策略”,但无需关心这个策略内部是打8折还是减50。

from abc import ABC, abstractmethod

# 1. 策略接口 (Strategy Interface)
class DiscountStrategy(ABC):
    @abstractmethod
    def apply(self, total):
        pass

# 2. 具体策略类 (Concrete Strategies)
class NoDiscount(DiscountStrategy):
    def apply(self, total):
        return total

class VipDiscount(DiscountStrategy):
    def apply(self, total):
        return total * 0.8  # 打8折

class BlackFridayDiscount(DiscountStrategy):
    def apply(self, total):
        return total * 0.5 if total > 200 else total * 0.9

# 3. 上下文类 (Context) —— 持有策略引用
class ShoppingCart:
    def __init__(self, total, strategy: DiscountStrategy):
        self.total = total
        self._strategy = strategy

    def set_strategy(self, strategy):
        self._strategy = strategy

    def checkout(self):
        final_price = self._strategy.apply(self.total)
        print(f"💰 原价: {self.total}, 实付: {final_price}")
        return final_price

# 4. 客户端调用 (Client)
if __name__ == "__main__":
    cart = ShoppingCart(300, NoDiscount())
    cart.checkout()  # 实付 300

    cart.set_strategy(VipDiscount())
    cart.checkout()  # 实付 240

    cart.set_strategy(BlackFridayDiscount())
    cart.checkout()  # 实付 150

✅ 策略模式的效果:客户端承担了 “选择” 的责任(明确指定用 VipDiscount 还是 BlackFridayDiscount),但把 “怎么算” 完全委托给了具体的策略类。算法之间相互独立,互不影响。

六、🎯 总结:如何做技术选型?

场景推荐模式
觉得“这堆代码好乱,调用太麻烦”,想整理一下外观模式
出现大量 if type == A then ... else if type == B...,且算法经常新增或修改策略模式
整合第三方库或老旧遗留系统外观模式
业务规则频繁变动,需要灵活扩展策略模式

记忆口诀

  • 如果你写代码是为了 “少写几行调用” 👉 考虑 外观模式
  • 如果你写代码是为了 “少改几行逻辑” 👉 考虑 策略模式

最终一句话

外观模式让“使用”变得简单,策略模式让“扩展”变得简单。 两者可以共存,例如一个外观类内部,可能同时使用了好几种策略来完成一个复杂的业务流程。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐