python设计模式–外观模式(Facade Pattern)

Python 设计模式——外观模式(Facade Pattern)详解

本文面向零基础新手,从概念到“简化入口”、从简单示例到完整子系统,全方位讲解外观模式。


一、什么是外观模式?

1.1 通俗理解

外观模式是一种结构型设计模式,核心思想是:

为一个复杂的子系统(多个类、多个接口)提供一个统一的、简化的“外观”接口;客户只和这个外观打交道,由外观在内部去调用子系统的各个部分,从而让客户不必了解子系统内部有多少类、调用顺序如何,降低使用难度和耦合。

可以这样类比:

  • 现实中的外观:看电影要开投影、关灯、开音响、拉窗帘、选片……若你每次自己操作七八个设备,很麻烦。影院提供一个“一键观影”按钮(外观):你只按一下,内部按正确顺序把投影、灯、音响、窗帘都处理好。你不需要知道先关灯还是先开投影,一个入口、一套简单操作就行。
  • 代码里:下单要查库存、扣库存、调支付、创建订单、发通知、扣积分……若业务代码到处直接调库存类、支付类、订单类,会乱且难维护。做一个“订单外观”:对外只暴露 place_order(用户, 商品, 数量),内部按顺序调用库存、支付、订单、通知等,客户只调一个方法

所以,外观模式解决的是:子系统复杂、调用方不想(或不该)关心内部细节和调用顺序时,用一个“门面”把复杂操作包起来,提供简单、统一的入口。

1.2 为什么需要外观?

场景一:子系统类多、调用复杂

例如“启动应用”:要读配置、初始化数据库连接、初始化日志、注册路由、启动服务器……若每处启动代码都自己写这一串,容易漏步骤、顺序错。做一个 ApplicationFacade.start(),内部按固定流程调用各个模块,一处实现、到处复用

场景二:降低客户与子系统的耦合

客户本来要依赖 N 个类(库存、支付、物流、通知),现在只依赖一个外观类;以后子系统内部重构(如合并两个类、改调用顺序),只要外观的对外接口不变,客户代码不用改。

场景三:分层清晰

外观成为“子系统对外的唯一入口”,便于理解:要做事就找外观,外观再协调内部。也便于测试:可以 mock 外观,或只测外观的编排逻辑。

1.3 适用场景(什么时候用?)

场景 说明
子系统复杂、调用步骤多 希望“一步到位”完成一系列操作,而不在业务代码里写一长串调用。
要降低客户与子系统的耦合 客户只依赖外观,不直接依赖内部多个类。
要为子系统提供清晰入口 分层、模块化,外观即“门面”。
遗留系统包装 老系统接口混乱,用外观包一层,对外提供简洁接口。

简单记忆:有一堆类、一堆步骤,希望一个简单入口完成一串操作时,用外观。


二、外观模式的结构(两个角色)

角色 说明
外观(Facade) 对外提供少量、简单的方法;内部持有或创建子系统的各个组件,在方法里按顺序调用它们,完成一个完整流程。
子系统(Subsystem) 多个类/模块,各自负责一部分功能;外观负责“编排”它们,客户不直接调它们(或只在少数场景下直接调)。

关系可以理解为:

  • 客户只依赖 外观,调用外观的方法(如 start()place_order())。
  • 外观内部依赖 子系统的多个类,在实现里依次调用:先 A,再 B,再 C……客户不需要知道 A、B、C 的存在和顺序。

三、示例一:一键观影(最经典)

需求:观影要:开投影、关灯、开音响、拉窗帘、播放。客户不想记顺序,只要“开始观影”;结束后要“结束观影”:关投影、开灯、关音响、拉窗帘。

3.1 子系统类(多个、各自独立)

class Projector:
    def on(self):
        print("投影仪 开")

    def off(self):
        print("投影仪 关")

class Light:
    def on(self):
        print("灯 开")

    def off(self):
        print("灯 关")

class Sound:
    def on(self):
        print("音响 开")

    def off(self):
        print("音响 关")

class Curtain:
    def down(self):
        print("窗帘 拉下")

    def up(self):
        print("窗帘 拉开")

class Player:
    def play(self, name: str):
        print(f"播放: {name}")

    def stop(self):
        print("停止播放")

3.2 外观:统一入口

class HomeTheaterFacade:
    """家庭影院外观:一键观影、一键结束"""
    def __init__(self):
        self._projector = Projector()
        self._light = Light()
        self._sound = Sound()
        self._curtain = Curtain()
        self._player = Player()

    def watch_movie(self, name: str):
        """开始观影:按正确顺序启动各设备"""
        print("=== 开始观影 ===")
        self._curtain.down()
        self._light.off()
        self._projector.on()
        self._sound.on()
        self._player.play(name)

    def end_movie(self):
        """结束观影:按正确顺序关闭"""
        print("=== 结束观影 ===")
        self._player.stop()
        self._sound.off()
        self._projector.off()
        self._light.on()
        self._curtain.up()

3.3 客户只调外观

facade = HomeTheaterFacade()
facade.watch_movie("泰坦尼克号")
# === 开始观影 ===
# 窗帘 拉下 / 灯 关 / 投影仪 开 / 音响 开 / 播放: 泰坦尼克号

facade.end_movie()
# === 结束观影 ===
# 停止播放 / 音响 关 / 投影仪 关 / 灯 开 / 窗帘 拉开

要点:客户不接触 Projector、Light、Sound 等,只调 watch_movieend_movie;顺序和细节都在外观里,改顺序只改外观即可。


四、示例二:下单流程(库存 + 支付 + 订单 + 通知)

需求:下单要:检查库存、扣库存、扣款、创建订单、发通知。用外观把这一串包成 place_order(用户, 商品ID, 数量)

4.1 子系统(简化)

class Inventory:
    def check(self, product_id: str, qty: int) -> bool:
        print(f"  [库存] 检查 {product_id} x{qty}")
        return True

    def deduct(self, product_id: str, qty: int):
        print(f"  [库存] 扣减 {product_id} x{qty}")

class Payment:
    def pay(self, user_id: str, amount: float) -> bool:
        print(f"  [支付] 用户 {user_id} 支付 {amount} 元")
        return True

class OrderService:
    def create(self, user_id: str, product_id: str, qty: int, amount: float):
        print(f"  [订单] 创建订单 user={user_id} product={product_id} qty={qty} amount={amount}")

class Notifier:
    def send(self, user_id: str, msg: str):
        print(f"  [通知] -> {user_id}: {msg}")

4.2 外观:下单

class OrderFacade:
    def __init__(self):
        self._inventory = Inventory()
        self._payment = Payment()
        self._order = OrderService()
        self._notifier = Notifier()

    def place_order(self, user_id: str, product_id: str, qty: int, unit_price: float) -> bool:
        """下单:检查库存 -> 扣库存 -> 支付 -> 创建订单 -> 通知"""
        if not self._inventory.check(product_id, qty):
            return False
        self._inventory.deduct(product_id, qty)
        amount = qty * unit_price
        if not self._payment.pay(user_id, amount):
            return False
        self._order.create(user_id, product_id, qty, amount)
        self._notifier.send(user_id, f"订单已创建,金额 {amount} 元")
        return True

4.3 使用

facade = OrderFacade()
facade.place_order("user1", "P001", 2, 99.0)
# 客户只调一行,内部按顺序走完库存、支付、订单、通知

五、示例三:应用启动外观

需求:启动应用要:加载配置、初始化数据库、初始化日志、注册路由、启动 HTTP 服务。用外观包成 start()

class ConfigLoader:
    def load(self):
        print("  [配置] 加载完成")

class Database:
    def connect(self):
        print("  [数据库] 连接完成")

class Logger:
    def init(self):
        print("  [日志] 初始化完成")

class Router:
    def register(self):
        print("  [路由] 注册完成")

class HttpServer:
    def run(self):
        print("  [HTTP] 服务已启动")

class ApplicationFacade:
    def __init__(self):
        self._config = ConfigLoader()
        self._db = Database()
        self._logger = Logger()
        self._router = Router()
        self._server = HttpServer()

    def start(self):
        print("=== 启动应用 ===")
        self._config.load()
        self._logger.init()
        self._db.connect()
        self._router.register()
        self._server.run()
        print("=== 启动完成 ===")

# 使用
app = ApplicationFacade()
app.start()

客户只调 app.start(),不必关心先配配置还是先连数据库;顺序变更只改外观。


六、外观 vs 其他模式

模式 目的 与外观的区别
外观 为子系统提供简化、统一的入口 不改变子系统接口,只是“包一层”、编排调用。
适配器 不兼容的接口能一起用 侧重接口转译;外观侧重“简化调用”、不转译接口。
中介者 多对象之间互相通信由中介协调 中介者管的是“对象间交互”;外观是“客户 -> 外观 -> 子系统”的单向编排。

简单记忆:外观 = 一个简单入口 + 内部按顺序调多个类,不改变子系统类的接口。


七、常见问题与注意点

7.1 客户还能直接调子系统吗?

可以。外观只是“推荐入口”,并不禁止客户在特殊场景下直接使用子系统类;但常规流程应通过外观,以便统一维护顺序和逻辑。

7.2 外观会不会变成“上帝类”?

若子系统非常庞大,一个外观里塞满几十个类、几十个步骤,外观会变得难维护。可以按“子流程”拆成多个小外观(如 OrderFacade、RefundFacade),或让外观只编排“子模块的入口”,子模块内部再有自己的小外观或流程。

7.3 何时用外观、何时直接调?

当“一系列步骤”在多处重复、或希望客户与子系统解耦、或需要统一入口时,用外观;当只是单次、简单调用一两个类时,直接调即可。


八、小结

  • 外观模式:为复杂子系统提供一个简化的统一入口(外观类);客户只和外观打交道,外观在内部按顺序调用子系统的多个类,完成完整流程,从而降低使用难度、降低客户与子系统的耦合
  • 两个角色:外观(Facade)、子系统(多个类);客户只依赖外观。
  • 典型用途:一键观影、下单流程、应用启动、编译流程等“多步骤、多类协作”的场景。
  • 实现要点:外观持有或创建子系统组件,在对外方法里按固定顺序调用它们。

建议先写“一键观影”或“应用启动”一例,再写“下单外观”,体会“一个方法里编排多步、客户只调一个入口”,这样对外观模式会掌握得比较扎实。

发表评论