外观模式(Facade):统一接口、子系统解耦、Tomcat中的外观模式

外观模式,说白了就是给一套复杂的子系统提供一个简单的门面。你想想看,你去餐厅吃饭,不需要知道后厨怎么切菜、怎么炒菜、怎么摆盘,你只需要跟服务员点单就行。服务员就是那个"外观"。

我刚开始做架构设计时,总觉得把每个子系统都暴露给客户端才够灵活。结果呢?客户端代码跟屎一样,到处都是new这个、调那个,耦合得死死的。后来我才明白——有时候,少即是多

什么是外观模式

外观模式属于结构型设计模式。它为一组复杂的子系统接口提供一个统一的、更高级的接口。这个接口让子系统更容易使用。

核心思想就三点:

  • 统一接口:对外只暴露一个门面类
  • 子系统解耦:客户端不直接依赖子系统
  • 简化调用:把复杂的调用链封装起来

核心要点:外观模式不限制客户端直接访问子系统。如果你需要更细粒度的控制,完全可以绕过外观。它只是提供一个"便捷通道"。

模式结构

外观模式的结构其实很简单,就三个角色:

角色 说明
Facade(外观) 知道哪些子系统负责处理哪些请求。将客户端请求委派给相应的子系统对象。
Subsystem(子系统) 实现子系统的功能。处理由Facade指派的任务。没有Facade的任何信息。
Client(客户端) 通过调用Facade提供的方法来完成功能,不需要直接与子系统交互。

这里我画了一张结构图,帮你理清关系:

Client 调用 Facade 统一接口 委派 Subsystem A classA.method1() Subsystem B classB.method2() Subsystem C classC.method3() 外观模式:客户端只跟Facade打交道 子系统之间可以互相调用,但客户端不需要知道

Java代码示例:一个简单的家庭影院

我记得有一次帮朋友做智能家居项目,就用了外观模式。这里我简化一下,用家庭影院来演示。

先看子系统:

// 子系统:投影仪
public class Projector {
    public void on() { System.out.println("投影仪打开"); }
    public void setInput(String source) { System.out.println("输入源设置为:" + source); }
}

// 子系统:音响
public class SoundSystem {
    public void on() { System.out.println("音响打开"); }
    public void setVolume(int level) { System.out.println("音量设置为:" + level); }
}

// 子系统:播放器
public class DVDPlayer {
    public void play(String movie) { System.out.println("播放电影:" + movie); }
}

再看外观类:

// 外观类
public class HomeTheaterFacade {
    private Projector projector;
    private SoundSystem sound;
    private DVDPlayer dvd;
    
    public HomeTheaterFacade() {
        this.projector = new Projector();
        this.sound = new SoundSystem();
        this.dvd = new DVDPlayer();
    }
    
    public void watchMovie(String movie) {
        System.out.println("=== 准备看电影 ===");
        projector.on();
        projector.setInput("DVD");
        sound.on();
        sound.setVolume(20);
        dvd.play(movie);
    }
}

客户端调用:

public class Client {
    public static void main(String[] args) {
        HomeTheaterFacade homeTheater = new HomeTheaterFacade();
        homeTheater.watchMovie("盗梦空间");
    }
}

你看,客户端只需要调用一个 watchMovie() 方法,背后投影仪、音响、播放器怎么配合,客户端完全不用管。这就是外观模式的价值。

Tomcat中的外观模式

说到实际应用,Tomcat里大量使用了外观模式。我当年看Tomcat源码时,发现一个很有意思的设计——RequestFacadeResponseFacade

Tomcat为什么要这么做?

原因很简单:安全。Tomcat内部有 HttpServletRequest 的实现类 Request,这个类有很多内部方法(比如 getCoyoteRequest()setRequestedSessionId())。如果直接把 Request 对象传给Servlet,那程序员就能调用这些内部方法,搞不好就把Tomcat搞崩了。

所以Tomcat搞了个 RequestFacade,它只暴露 HttpServletRequest 接口中定义的方法。Servlet拿到的是外观对象,不是真正的内部实现。

经验之谈:外观模式在框架设计中特别适合用来做"接口隔离"。你不想暴露给外界的内部方法,通通藏在外观后面。我曾经在一个支付系统中也这么干过——对外只暴露 pay()refund() 几个方法,内部几十个类的协作全部封装起来。

C++代码示例

C++的实现思路完全一样,只是语法不同:

#include <iostream>
#include <string>
using namespace std;

// 子系统
class CPU {
public:
    void start() { cout << "CPU启动" << endl; }
};

class Memory {
public:
    void load() { cout << "内存加载" << endl; }
};

class HardDrive {
public:
    void read() { cout << "硬盘读取" << endl; }
};

// 外观
class ComputerFacade {
private:
    CPU cpu;
    Memory memory;
    HardDrive hardDrive;
public:
    void startComputer() {
        cout << "=== 启动电脑 ===" << endl;
        cpu.start();
        memory.load();
        hardDrive.read();
        cout << "电脑启动完成" << endl;
    }
};

int main() {
    ComputerFacade computer;
    computer.startComputer();
    return 0;
}

什么时候用外观模式

我个人总结了三个场景:

  1. 子系统太复杂:客户端需要跟十几个类打交道,调用顺序还特别讲究。这时候外观就是救星。
  2. 需要分层:你想把系统分成多层,每层之间通过外观来通信。这样层与层之间就解耦了。
  3. 第三方库封装:你引入了一个第三方库,但不想让业务代码直接依赖它。包一层外观,以后换库也方便。

注意:外观模式不是万能药。不要为了用而用。如果一个子系统本来就很简单,硬加个外观就是画蛇添足。我曾经见过一个项目,每个类都配一个外观,结果代码量翻了一倍,维护起来反而更痛苦。

避坑指南

这里说几个我踩过的坑:

  • 外观不要变成上帝类:外观只做"委派"工作,不要把业务逻辑写进去。否则外观会变得臃肿不堪。
  • 不要滥用外观:如果客户端需要频繁绕过外观直接调用子系统,说明外观设计得不对。要么拆分成多个外观,要么干脆别用。
  • 外观的粒度要适中:一个外观管太多子系统,或者一个子系统拆成多个外观,都不好。我习惯按业务功能来划分——一个业务功能对应一个外观。

嗯,外观模式就讲到这里。它不是什么高深的技术,但用好了能让代码结构清晰很多。你想想看,你现在的项目里,有没有哪些地方可以用外观来简化?


公众号:蓝海资料掘金营,微信deep3321