Skip to content

外观模式(Facade Pattern) ​

一、模式概述 ​

外观模式是一种结构型设计模式,它为子系统中的一组接口提供一个统一的高层接口。外观模式定义了一个更高层次的接口,使得子系统更加容易使用。

核心思想:隐藏系统的复杂性,只暴露一个简单的接口给客户端。就像餐厅的服务员,你只需要跟服务员点菜,而不需要自己去厨房告诉每个厨师怎么做。

二、角色说明 ​

角色说明
Facade(外观角色)知道哪些子系统类负责处理请求,将客户端的请求代理给合适的子系统对象
SubSystem(子系统角色)实现子系统功能,处理由 Facade 对象指派的任务,不知道 Facade 的存在
Client(客户端)通过外观角色来调用子系统的功能

三、生活中的例子 ​

  • 电脑开机:按下电源按钮,外观模式帮你完成 CPU初始化、内存检测、硬盘加载、操作系统启动等一系列复杂操作
  • 医院导诊台:你不需要分别去挂号、检查、化验,导诊台帮你安排好一切
  • 智能家居:说一句"我要睡觉了",外观模式帮你关灯、拉窗帘、锁门、开空调

四、示例1:家庭影院外观 ​

问题场景 ​

家庭影院包含多个设备:投影仪、音响、DVD播放器、灯光。每次看电影都需要操作这些设备,非常繁琐。

代码实现 ​

java
// ========== 子系统:各个独立设备 ==========

/**
 * 投影仪
 */
class Projector {
    public void on() {
        System.out.println("投影仪开机...");
    }

    public void off() {
        System.out.println("投影仪关机...");
    }

    public void wideScreenMode() {
        System.out.println("投影仪切换到宽屏模式...");
    }
}

/**
 * 音响系统
 */
class Amplifier {
    public void on() {
        System.out.println("功放开机...");
    }

    public void off() {
        System.out.println("功放关机...");
    }

    public void setVolume(int level) {
        System.out.println("设置音量为: " + level);
    }

    public void setSurroundSound() {
        System.out.println("开启环绕声...");
    }
}

/**
 * DVD播放器
 */
class DvdPlayer {
    public void on() {
        System.out.println("DVD播放器开机...");
    }

    public void off() {
        System.out.println("DVD播放器关机...");
    }

    public void play(String movie) {
        System.out.println("正在播放电影: " + movie);
    }

    public void stop() {
        System.out.println("停止播放...");
    }
}

/**
 * 灯光系统
 */
class TheaterLights {
    public void on() {
        System.out.println("灯光打开...");
    }

    public void off() {
        System.out.println("灯光关闭...");
    }

    public void dim(int level) {
        System.out.println("灯光调暗到: " + level + "%");
    }
}

/**
 * 屏幕
 */
class Screen {
    public void up() {
        System.out.println("屏幕上卷...");
    }

    public void down() {
        System.out.println("屏幕下降...");
    }
}

// ========== 外观角色 ==========

/**
 * 家庭影院外观
 * 封装了所有设备的复杂操作,提供简单的接口
 */
class HomeTheaterFacade {
    private Projector projector;
    private Amplifier amplifier;
    private DvdPlayer dvdPlayer;
    private TheaterLights lights;
    private Screen screen;

    public HomeTheaterFacade() {
        this.projector = new Projector();
        this.amplifier = new Amplifier();
        this.dvdPlayer = new DvdPlayer();
        this.lights = new TheaterLights();
        this.screen = new Screen();
    }

    /**
     * 一键观影模式:所有设备自动就绪
     */
    public void watchMovie(String movie) {
        System.out.println("=== 准备观影: " + movie + " ===");
        lights.dim(10);
        screen.down();
        projector.on();
        projector.wideScreenMode();
        amplifier.on();
        amplifier.setSurroundSound();
        amplifier.setVolume(8);
        dvdPlayer.on();
        dvdPlayer.play(movie);
        System.out.println("=== 一切就绪,请享受电影! ===\n");
    }

    /**
     * 一键关闭:所有设备自动关闭
     */
    public void endMovie() {
        System.out.println("=== 关闭家庭影院 ===");
        dvdPlayer.stop();
        dvdPlayer.off();
        amplifier.off();
        projector.off();
        screen.up();
        lights.on();
        System.out.println("=== 家庭影院已关闭 ===\n");
    }

    /**
     * 暂停模式
     */
    public void pauseMovie() {
        System.out.println("=== 暂停播放 ===");
        lights.dim(50);
        dvdPlayer.stop();
        System.out.println("=== 已暂停 ===\n");
    }
}

// ========== 客户端代码 ==========

public class HomeTheaterDemo {
    public static void main(String[] args) {
        HomeTheaterFacade theater = new HomeTheaterFacade();

        // 以前需要这样操作(复杂)
        // theater.lights.dim(10);
        // theater.screen.down();
        // theater.projector.on();
        // ... 一堆操作

        // 现在只需要一行代码
        theater.watchMovie("流浪地球");
        theater.pauseMovie();
        theater.endMovie();
    }
}

运行结果 ​

=== 准备观影: 流浪地球 ===
灯光调暗到: 10%
屏幕下降...
投影仪开机...
投影仪切换到宽屏模式...
功放开机...
开启环绕声...
设置音量为: 8
DVD播放器开机...
正在播放电影: 流浪地球
=== 一切就绪,请享受电影! ===

=== 暂停播放 ===
灯光调暗到: 50%
停止播放...
=== 已暂停 ===

=== 关闭家庭影院 ===
停止播放...
DVD播放器关机...
功放关机...
投影仪关机...
屏幕上卷...
灯光打开...
=== 家庭影院已关闭 ===

五、示例2:订单处理外观 ​

问题场景 ​

一个电商订单处理涉及库存检查、价格计算、支付处理、物流安排、通知发送等多个子系统。

java
// ========== 子系统 ==========

/**
 * 库存子系统
 */
class InventoryService {
    public boolean checkStock(String productId, int quantity) {
        System.out.println("[库存] 检查商品 " + productId + " 库存,数量: " + quantity);
        return true; // 假设库存充足
    }

    public void deductStock(String productId, int quantity) {
        System.out.println("[库存] 扣减商品 " + productId + " 库存 " + quantity + " 件");
    }
}

/**
 * 价格子系统
 */
class PricingService {
    public double calculatePrice(String productId, int quantity) {
        double price = 99.0 * quantity;
        System.out.println("[价格] 计算价格: " + quantity + " x 99.0 = " + price);
        return price;
    }

    public double applyDiscount(double price, String couponCode) {
        double discounted = price * 0.9;
        System.out.println("[价格] 使用优惠券 " + couponCode + ",折后价: " + discounted);
        return discounted;
    }
}

/**
 * 支付子系统
 */
class PaymentService {
    public boolean processPayment(double amount, String method) {
        System.out.println("[支付] 使用 " + method + " 支付 " + amount + " 元");
        return true;
    }
}

/**
 * 物流子系统
 */
class ShippingService {
    public String arrangeShipping(String orderId, String address) {
        String trackingNo = "SF" + System.currentTimeMillis();
        System.out.println("[物流] 订单 " + orderId + " 已安排发货至 " + address);
        System.out.println("[物流] 快递单号: " + trackingNo);
        return trackingNo;
    }
}

/**
 * 通知子系统
 */
class NotificationService {
    public void sendOrderConfirmation(String orderId, String email) {
        System.out.println("[通知] 订单确认邮件已发送至 " + email);
    }

    public void sendShippingNotification(String orderId, String trackingNo) {
        System.out.println("[通知] 发货通知已发送,快递单号: " + trackingNo);
    }
}

// ========== 外观角色 ==========

/**
 * 订单处理外观
 * 将多个子系统的复杂流程封装为简单的一键操作
 */
class OrderFacade {
    private InventoryService inventory;
    private PricingService pricing;
    private PaymentService payment;
    private ShippingService shipping;
    private NotificationService notification;

    public OrderFacade() {
        this.inventory = new InventoryService();
        this.pricing = new PricingService();
        this.payment = new PaymentService();
        this.shipping = new ShippingService();
        this.notification = new NotificationService();
    }

    /**
     * 一键下单:检查库存 -> 计算价格 -> 支付 -> 发货 -> 通知
     */
    public boolean placeOrder(String productId, int quantity,
                               String couponCode, double totalAmount,
                               String paymentMethod, String address,
                               String orderId, String email) {
        System.out.println("========== 开始处理订单: " + orderId + " ==========\n");

        // 1. 检查库存
        if (!inventory.checkStock(productId, quantity)) {
            System.out.println("库存不足,订单处理失败!");
            return false;
        }

        // 2. 计算价格
        double price = pricing.calculatePrice(productId, quantity);
        if (couponCode != null) {
            price = pricing.applyDiscount(price, couponCode);
        }

        // 3. 处理支付
        if (!payment.processPayment(price, paymentMethod)) {
            System.out.println("支付失败,订单处理失败!");
            return false;
        }

        // 4. 扣减库存
        inventory.deductStock(productId, quantity);

        // 5. 安排发货
        String trackingNo = shipping.arrangeShipping(orderId, address);

        // 6. 发送通知
        notification.sendOrderConfirmation(orderId, email);
        notification.sendShippingNotification(orderId, trackingNo);

        System.out.println("\n========== 订单 " + orderId + " 处理完成 ==========\n");
        return true;
    }

    /**
     * 一键取消订单
     */
    public void cancelOrder(String orderId, String productId, int quantity) {
        System.out.println("========== 取消订单: " + orderId + " ==========");
        System.out.println("[取消] 订单 " + orderId + " 已取消");
        System.out.println("[库存] 恢复商品 " + productId + " 库存 " + quantity + " 件");
        System.out.println("[支付] 退款处理中...");
        System.out.println("[通知] 取消通知已发送");
        System.out.println("========== 订单取消完成 ==========\n");
    }
}

// ========== 客户端代码 ==========

public class OrderFacadeDemo {
    public static void main(String[] args) {
        OrderFacade orderFacade = new OrderFacade();

        // 以前需要分别调用各个子系统
        // inventory.checkStock(...)
        // pricing.calculatePrice(...)
        // payment.processPayment(...)
        // shipping.arrangeShipping(...)
        // notification.sendOrderConfirmation(...)
        // ... 十几行代码

        // 现在只需要一行
        orderFacade.placeOrder(
            "SKU001", 2, "VIP10",
            198.0, "微信支付",
            "北京市海淀区",
            "ORD20250101001",
            "user@example.com"
        );

        orderFacade.cancelOrder("ORD20250101001", "SKU001", 2);
    }
}

六、UML 类图 ​

┌──────────────┐
│    Client     │
└──────┬───────┘
       │ 调用
       ▼
┌──────────────────┐
│  HomeTheaterFacade │ ←── 外观角色
│  + watchMovie()    │
│  + endMovie()      │
└──────┬───────────┘
       │ 组合
       ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Projector │ │Amplifier │ │DvdPlayer │ │  Lights  │  ←── 子系统
│ +on()     │ │ +on()    │ │ +on()    │ │ +on()    │
│ +off()    │ │ +off()   │ │ +play()  │ │ +dim()   │
└──────────┘ └──────────┘ └──────────┘ └──────────┘

七、优缺点分析 ​

优点 ​

  1. 简化接口:客户端代码变得非常简洁,不需要了解子系统的复杂细节
  2. 解耦:客户端与子系统之间没有直接耦合关系,通过外观角色间接交互
  3. 符合迪米特法则(最少知识原则):客户端只需要知道外观角色即可
  4. 分层架构:可以在子系统和客户端之间增加外观层,提高层次独立性

缺点 ​

  1. 不够灵活:如果需要添加新的子系统方法,可能需要修改外观类
  2. 过度使用:如果系统本身并不复杂,强行使用外观模式反而增加了复杂度
  3. 违背开闭原则:新增子系统可能需要修改外观类的代码

八、使用场景 ​

  • 复杂子系统简化:当一个系统很复杂时,提供一个简单的接口
  • 分层架构:在每层定义入口点,层与层之间通过外观交互
  • 遗留系统集成:为遗留系统提供一个简单的接口,隔离旧系统的变化
  • 模块化设计:为子系统提供统一的接口,方便其他模块使用

九、与其他模式的关系 ​

  • 适配器模式 vs 外观模式:适配器改变接口以匹配客户端期望,外观模式简化接口但不改变接口
  • 中介者模式 vs 外观模式:中介者让子系统之间互相通信,外观模式只让客户端与子系统单向通信
  • 单例模式:外观对象通常可以设计为单例

十、最佳实践 ​

  1. 不要过度设计:如果一个方法调用就能解决问题,不需要外观模式
  2. 保持外观的稳定性:外观类的接口一旦确定,尽量不要频繁修改
  3. 可以有多个外观:同一个子系统可以有多个不同层次的外观
  4. 外观不是万能的:当客户端需要直接访问子系统时,应该绕过外观直接调用

总结:外观模式是简化复杂系统的利器,它通过提供统一的高层接口,让客户端可以轻松使用复杂的子系统。在实际开发中,我们经常会无意中使用到外观模式,比如封装数据库操作、统一API接口等。关键在于找到真正需要简化的复杂度,而不是为了使用模式而使用模式。