外观模式(Facade):统一接口、子系统解耦、Tomcat中的外观模式
外观模式,说白了就是给一套复杂的子系统提供一个简单的门面。你想想看,你去餐厅吃饭,不需要知道后厨怎么切菜、怎么炒菜、怎么摆盘,你只需要跟服务员点单就行。服务员就是那个"外观"。
我刚开始做架构设计时,总觉得把每个子系统都暴露给客户端才够灵活。结果呢?客户端代码跟屎一样,到处都是new这个、调那个,耦合得死死的。后来我才明白——有时候,少即是多。
什么是外观模式
外观模式属于结构型设计模式。它为一组复杂的子系统接口提供一个统一的、更高级的接口。这个接口让子系统更容易使用。
核心思想就三点:
- 统一接口:对外只暴露一个门面类
- 子系统解耦:客户端不直接依赖子系统
- 简化调用:把复杂的调用链封装起来
核心要点:外观模式不限制客户端直接访问子系统。如果你需要更细粒度的控制,完全可以绕过外观。它只是提供一个"便捷通道"。
模式结构
外观模式的结构其实很简单,就三个角色:
| 角色 | 说明 |
|---|---|
| Facade(外观) | 知道哪些子系统负责处理哪些请求。将客户端请求委派给相应的子系统对象。 |
| Subsystem(子系统) | 实现子系统的功能。处理由Facade指派的任务。没有Facade的任何信息。 |
| Client(客户端) | 通过调用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源码时,发现一个很有意思的设计——RequestFacade 和 ResponseFacade。
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;
}
什么时候用外观模式
我个人总结了三个场景:
- 子系统太复杂:客户端需要跟十几个类打交道,调用顺序还特别讲究。这时候外观就是救星。
- 需要分层:你想把系统分成多层,每层之间通过外观来通信。这样层与层之间就解耦了。
- 第三方库封装:你引入了一个第三方库,但不想让业务代码直接依赖它。包一层外观,以后换库也方便。
注意:外观模式不是万能药。不要为了用而用。如果一个子系统本来就很简单,硬加个外观就是画蛇添足。我曾经见过一个项目,每个类都配一个外观,结果代码量翻了一倍,维护起来反而更痛苦。
避坑指南
这里说几个我踩过的坑:
- 外观不要变成上帝类:外观只做"委派"工作,不要把业务逻辑写进去。否则外观会变得臃肿不堪。
- 不要滥用外观:如果客户端需要频繁绕过外观直接调用子系统,说明外观设计得不对。要么拆分成多个外观,要么干脆别用。
- 外观的粒度要适中:一个外观管太多子系统,或者一个子系统拆成多个外观,都不好。我习惯按业务功能来划分——一个业务功能对应一个外观。
嗯,外观模式就讲到这里。它不是什么高深的技术,但用好了能让代码结构清晰很多。你想想看,你现在的项目里,有没有哪些地方可以用外观来简化?
公众号:蓝海资料掘金营,微信deep3321