Skip to content

外观模式 (Facade Pattern)

一、定义

一句话概括:为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。

官方定义(GoF):Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

外观模式的核心思想是:引入一个外观类(Facade),封装子系统的复杂性,对外提供简单、清晰的接口。客户端只需要与外观类交互,而不需要了解子系统内部的复杂细节。


二、解决的问题

2.1 什么场景下需要外观模式?

  • 简化复杂系统:一个子系统包含多个类,客户端需要依次调用多个方法才能完成一个业务操作。
  • 解耦客户端与子系统:降低客户端与子系统之间的耦合度,子系统的变化不影响客户端。
  • 分层架构:为每一层提供统一的入口,如 Service 层调用多个 DAO、Gateway 调用多个微服务。
  • 遗留系统封装:将复杂的遗留系统包装成简单接口,方便新系统调用。

2.2 不用外观模式会有什么问题?

假设一个"订单下单"流程需要依次调用:库存服务、支付服务、物流服务、通知服务。

java
// 不用外观模式 —— 客户端需要了解所有子系统
public void placeOrder(Order order) {
    InventoryService inventory = new InventoryService();
    PaymentService payment = new PaymentService();
    LogisticsService logistics = new LogisticsService();
    NotificationService notification = new NotificationService();

    boolean inStock = inventory.checkStock(order.getProductId());
    if (!inStock) throw new RuntimeException("库存不足");

    boolean paid = payment.pay(order.getUserId(), order.getAmount());
    if (!paid) throw new RuntimeException("支付失败");

    String trackingNo = logistics.createShipment(order.getId());
    notification.send(order.getUserId(), "订单已发货,运单号: " + trackingNo);
}
问题1:客户端耦合严重 —— 依赖 4 个服务,任一服务接口变化都要修改客户端
问题2:调用顺序复杂 —— 客户端需要知道正确的调用顺序,容易出错
问题3:代码重复 —— 每个下单的地方都要写同样的调用逻辑
问题4:难以测试 —— 客户端测试需要 mock 4 个服务

三、结构

3.1 文字描述

外观模式包含以下角色:

  • Facade(外观类):提供统一的高层接口,内部调用子系统各组件。
  • Subsystem Classes(子系统类):实现子系统的具体功能,处理 Facade 的委托。
  • Client(客户端):通过 Facade 与子系统交互,不直接调用子系统类。

3.2 ASCII 类图

                    ┌──────────────────────┐
                    │       Facade          │
                    ├──────────────────────┤
                    │ + simpleOperation()   │
                    │   subA.op1()          │
                    │   subB.op2()          │
                    │   subC.op3()          │
                    └──┬──────┬──────┬──────┘
                       │      │      │
              ┌────────┘      │      └────────┐
              ▼               ▼               ▼
        ┌──────────┐  ┌──────────┐  ┌──────────┐
        │ Subsystem│  │ Subsystem│  │ Subsystem│
        │    A     │  │    B     │  │    C     │
        ├──────────┤  ├──────────┤  ├──────────┤
        │ + op1()  │  │ + op2()  │  │ + op3()  │
        └──────────┘  └──────────┘  └──────────┘

        Client → Facade → Subsystem A/B/C
        (客户端不直接访问子系统)

四、代码实现

4.1 基础实现

场景:智能家居控制系统

java
// ============ 子系统类 ============
class Light {
    public void on()  { System.out.println("开灯"); }
    public void off() { System.out.println("关灯"); }
    public void dim(int level) { System.out.println("灯光调至 " + level + "%"); }
}

class AirConditioner {
    public void on()  { System.out.println("空调开启"); }
    public void off() { System.out.println("空调关闭"); }
    public void setTemperature(int temp) { System.out.println("空调温度设为 " + temp + "°C"); }
}

class Curtain {
    public void open()  { System.out.println("窗帘打开"); }
    public void close() { System.out.println("窗帘关闭"); }
}

class TV {
    public void on()  { System.out.println("电视开启"); }
    public void off() { System.out.println("电视关闭"); }
    public void setChannel(int channel) { System.out.println("调到频道 " + channel); }
}

// ============ Facade:智能家居外观 ============
class SmartHomeFacade {
    private Light light;
    private AirConditioner ac;
    private Curtain curtain;
    private TV tv;

    public SmartHomeFacade() {
        this.light = new Light();
        this.ac = new AirConditioner();
        this.curtain = new Curtain();
        this.tv = new TV();
    }

    // 回家模式:一键开启所有设备
    public void comeHome() {
        System.out.println("===== 回家模式 =====");
        curtain.open();
        light.on();
        light.dim(80);
        ac.on();
        ac.setTemperature(26);
        tv.on();
        tv.setChannel(1);
    }

    // 离家模式:一键关闭所有设备
    public void leaveHome() {
        System.out.println("===== 离家模式 =====");
        tv.off();
        ac.off();
        light.off();
        curtain.close();
    }

    // 睡眠模式:只保留空调
    public void sleepMode() {
        System.out.println("===== 睡眠模式 =====");
        tv.off();
        light.dim(10);
        curtain.close();
        ac.setTemperature(25);
    }
}

// ============ 客户端测试 ============
public class FacadeDemo {
    public static void main(String[] args) {
        SmartHomeFacade facade = new SmartHomeFacade();

        facade.comeHome();   // 一键回家
        // ... 用户活动 ...
        facade.sleepMode();  // 一键睡眠
        // ... 第二天 ...
        facade.leaveHome();  // 一键离家
    }
}

4.2 进阶实现

4.2.1 微服务网关外观

java
// ============ 微服务子系统 ============
class UserService {
    public UserDTO getUser(Long id) {
        return new UserDTO(id, "张三", "zhangsan@example.com");
    }
}

class OrderService {
    public List<OrderDTO> getOrdersByUserId(Long userId) {
        return Arrays.asList(
            new OrderDTO(1L, userId, 199.00),
            new OrderDTO(2L, userId, 299.00));
    }
}

class PaymentService {
    public PaymentInfo getPaymentInfo(Long orderId) {
        return new PaymentInfo(orderId, "PAID");
    }
}

class LogisticsService {
    public LogisticsInfo getLogistics(Long orderId) {
        return new LogisticsInfo(orderId, "IN_TRANSIT", "SF1234567890");
    }
}

// ============ DTO ============
class UserDTO {
    Long id; String name; String email;
    public UserDTO(Long id, String name, String email) {
        this.id = id; this.name = name; this.email = email;
    }
}

class OrderDTO {
    Long id; Long userId; Double amount;
    public OrderDTO(Long id, Long userId, Double amount) {
        this.id = id; this.userId = userId; this.amount = amount;
    }
}

class PaymentInfo {
    Long orderId; String status;
    public PaymentInfo(Long orderId, String status) {
        this.orderId = orderId; this.status = status;
    }
}

class LogisticsInfo {
    Long orderId; String status; String trackingNo;
    public LogisticsInfo(Long orderId, String status, String trackingNo) {
        this.orderId = orderId; this.status = status; this.trackingNo = trackingNo;
    }
}

// ============ Facade:API 网关 ============
class ApiGatewayFacade {
    private UserService userService;
    private OrderService orderService;
    private PaymentService paymentService;
    private LogisticsService logisticsService;

    public ApiGatewayFacade() {
        this.userService = new UserService();
        this.orderService = new OrderService();
        this.paymentService = new PaymentService();
        this.logisticsService = new LogisticsService();
    }

    // 用户详情页:聚合多个服务的数据
    public UserProfileDTO getUserProfile(Long userId) {
        // 1. 获取用户信息
        UserDTO user = userService.getUser(userId);

        // 2. 获取用户订单
        List<OrderDTO> orders = orderService.getOrdersByUserId(userId);

        // 3. 为每个订单获取支付和物流信息
        List<OrderDetailDTO> orderDetails = new ArrayList<>();
        for (OrderDTO order : orders) {
            PaymentInfo payment = paymentService.getPaymentInfo(order.id);
            LogisticsInfo logistics = logisticsService.getLogistics(order.id);
            orderDetails.add(new OrderDetailDTO(order, payment, logistics));
        }

        return new UserProfileDTO(user, orderDetails);
    }
}

// 聚合结果
class UserProfileDTO {
    UserDTO user;
    List<OrderDetailDTO> orders;
    public UserProfileDTO(UserDTO user, List<OrderDetailDTO> orders) {
        this.user = user; this.orders = orders;
    }
}

class OrderDetailDTO {
    OrderDTO order;
    PaymentInfo payment;
    LogisticsInfo logistics;
    public OrderDetailDTO(OrderDTO order, PaymentInfo payment, LogisticsInfo logistics) {
        this.order = order; this.payment = payment; this.logistics = logistics;
    }
}

4.2.2 复杂子系统简化

java
// ============ 视频转换子系统(复杂)============
class VideoFile { /* 视频文件 */ }
class AudioMixer { public AudioFile fix(AudioFile audio) { /* 修复音频 */ return new AudioFile(); } }
class AudioFile { /* 音频文件 */ }
class CodecFactory {
    public Codec extract(VideoFile file) { return new Codec(); }
}
class Codec { /* 编解码器 */ }
class BitrateReader {
    public static VideoFile read(VideoFile file, Codec codec) { return file; }
    public static VideoFile convert(VideoFile buffer, Codec codec) { return buffer; }
}
class OggCompressionCodec extends Codec { /* OGG 压缩 */ }
class MPEG4CompressionCodec extends Codec { /* MP4 压缩 */ }

// ============ Facade:视频转换外观 ============
class VideoConverter {
    public VideoFile convert(String fileName, String format) {
        VideoFile file = new VideoFile();
        Codec sourceCodec = new CodecFactory().extract(file);

        Codec destinationCodec;
        if ("mp4".equals(format)) {
            destinationCodec = new MPEG4CompressionCodec();
        } else {
            destinationCodec = new OggCompressionCodec();
        }

        VideoFile buffer = BitrateReader.read(file, sourceCodec);
        VideoFile result = BitrateReader.convert(buffer, destinationCodec);

        // 修复音频
        AudioMixer mixer = new AudioMixer();
        mixer.fix(new AudioFile());

        return result;
    }
}

// 客户端只需一行代码:
// VideoConverter converter = new VideoConverter();
// VideoFile mp4 = converter.convert("funny-cats.ogg", "mp4");

4.3 生产级实现

Spring Boot 订单服务外观

java
// ============ 订单提交外观 ============
@Service
class OrderFacade {
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private CouponService couponService;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private MessageProducer messageProducer;

    @Transactional(rollbackFor = Exception.class)
    public OrderResult submitOrder(OrderRequest request) {
        // 1. 参数校验
        validateRequest(request);

        // 2. 库存扣减(预占)
        boolean locked = inventoryService.lockStock(
            request.getSkuId(), request.getQuantity());
        if (!locked) {
            throw new BusinessException("库存不足");
        }

        // 3. 优惠券核销
        BigDecimal discount = BigDecimal.ZERO;
        if (request.getCouponId() != null) {
            discount = couponService.useCoupon(
                request.getUserId(), request.getCouponId());
        }

        // 4. 计算金额
        BigDecimal totalAmount = request.getUnitPrice()
            .multiply(BigDecimal.valueOf(request.getQuantity()))
            .subtract(discount);

        // 5. 创建订单
        Order order = new Order();
        order.setUserId(request.getUserId());
        order.setSkuId(request.getSkuId());
        order.setQuantity(request.getQuantity());
        order.setTotalAmount(totalAmount);
        order.setStatus(OrderStatus.PENDING_PAYMENT);
        orderRepository.save(order);

        // 6. 发起支付
        PaymentResult payment = paymentService.pay(
            order.getId(), totalAmount, request.getPayChannel());

        // 7. 发送消息通知
        messageProducer.sendOrderCreated(order);

        return new OrderResult(order.getId(), payment.getPayUrl());
    }

    @Transactional(rollbackFor = Exception.class)
    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new BusinessException("订单不存在"));

        // 1. 释放库存
        inventoryService.releaseStock(order.getSkuId(), order.getQuantity());

        // 2. 退还优惠券
        if (order.getCouponId() != null) {
            couponService.returnCoupon(order.getUserId(), order.getCouponId());
        }

        // 3. 退款
        paymentService.refund(order.getId());

        // 4. 更新状态
        order.setStatus(OrderStatus.CANCELLED);
        orderRepository.save(order);

        // 5. 发送通知
        messageProducer.sendOrderCancelled(order);
    }
}

五、优缺点

优点

优点说明
简化接口将复杂的子系统调用简化为一个高层接口
降低耦合客户端与子系统解耦,子系统变化不影响客户端
分层清晰符合分层架构思想,Facade 成为子系统入口
提高安全性可以屏蔽子系统中不安全的操作

缺点

缺点说明
不符合开闭原则新增子系统功能需要修改 Facade 类
可能成为上帝类Facade 承担过多职责,变成"上帝对象"
灵活性降低客户端只能通过 Facade 访问子系统,无法直接使用高级功能

六、适用场景

  1. 复杂子系统简化:为复杂的子系统提供简单入口,如第三方 SDK 封装。
  2. 分层架构:为每一层提供统一入口,Service 层封装多个 DAO 操作。
  3. 微服务聚合:API 网关聚合多个微服务的响应。
  4. 遗留系统封装:将复杂的遗留系统包装成简单接口。
  5. 工具类库:为复杂的工具类库提供便捷方法。
  6. 第三方集成:封装第三方 SDK 的复杂调用链。

七、JDK / Spring 框架中的实际应用

7.1 JDK 中的外观模式

java
// 1. java.net.URL —— 外观模式封装了复杂的网络操作
URL url = new URL("https://www.example.com");
URLConnection conn = url.openConnection();
// 内部封装了 DNS 解析、Socket 建立、协议处理等

// 2. javax.faces.context.FacesContext —— JSF 的外观
// 封装了 ServletContext、HttpSession、HttpServletRequest 等

// 3. Class.forName() —— 外观模式封装了类加载机制
Class<?> clazz = Class.forName("com.example.MyClass");

7.2 Spring 中的外观模式

java
// 1. JdbcTemplate —— 封装了 JDBC 的复杂操作
// 不用手动管理 Connection、Statement、ResultSet
jdbcTemplate.query("SELECT * FROM users", rowMapper);

// 2. RestTemplate —— 封装了 HTTP 请求的复杂操作
restTemplate.getForObject("http://api.example.com/users/1", User.class);

// 3. Spring MVC 的 DispatcherServlet
// 封装了请求分发、处理器映射、视图解析等复杂流程

// 4. Spring Data JPA 的 Repository
// 封装了 EntityManager、Transaction 等复杂操作

八、与其他模式的关系

模式关系
适配器模式适配器改变接口以适配不同系统,外观简化接口以方便使用。适配器通常是事后补救,外观通常是事前设计。
抽象工厂模式抽象工厂可以和外观模式配合,外观通过抽象工厂创建子系统对象。
中介者模式外观和中介者都封装了多个对象的交互,但外观是单向调用(Client→Facade→Subsystem),中介者是双向协调(同事之间互相通信)。
单例模式外观类通常设计为单例,因为只需要一个入口。
代理模式外观可以为子系统添加额外的代理逻辑,如缓存、权限校验。

九、面试常见问题

Q1:外观模式和适配器模式有什么区别?

  • 意图不同:外观模式简化接口,为复杂子系统提供统一入口;适配器模式转换接口,让不兼容的接口协同工作。
  • 使用时机不同:外观模式通常在设计阶段主动规划;适配器模式通常在维护阶段事后补救。
  • 方向不同:外观提供新接口来包装旧接口;适配器复用已有接口进行转换。

Q2:外观模式会违反开闭原则,如何解决?

:可以通过引入抽象外观类来解决。定义抽象外观接口,每个子系统版本对应一个具体外观实现。新增子系统时,创建新的外观实现类而不是修改原有外观类。

Q3:外观模式和中介者模式有什么区别?

  • 外观模式是单向的:Client → Facade → Subsystems,子系统之间不通过各种 Facade 通信。
  • 中介者模式是双向的:Colleague ↔ Mediator ↔ Colleague,同事对象通过中介者互相通信。
  • 外观是对外暴露的简化接口,中介者是内部协调的中心。

Q4:微服务架构中外观模式如何应用?

:API 网关(如 Spring Cloud Gateway)就是外观模式在微服务中的典型应用。网关将多个微服务的接口聚合为一个统一的对外 API,客户端不需要知道微服务的具体地址和调用方式。

Q5:外观模式是否意味着客户端不能直接访问子系统?

:不是。外观模式只是提供一个简化的入口,但并不禁止客户端直接访问子系统。如果客户端需要子系统的更高级功能,仍然可以直接调用子系统类。外观模式是可选的便利层。