设计模式之外观

什么是外观模式?

外观,也叫 Facade Pattern,就是给复杂的子系统提供一个 “统一的接待窗口”。想象你去政府办业务,如果没用外观模式,你需要跑房管局、税务局、民政局等 5 个窗口;有了外观模式,政务大厅设立了一个 “综合业务窗口”,你把材料给他,他负责在后台调动各个部门,你只需要跟这一个窗口打交道就可以了。


简单示例

假设你家有一个家庭影院,包含:灯光(Light)、投影仪(Projector)、音响(Stereo)。假设你不使用外观模式(手动挡),你需要亲手操作三个对象,代码很乱:

1
2
3
4
light.down();
projector.on();
stereo.on();
stereo.setVolume(10);


使用外观模式(一键观影):

第一步:定义子系统组件

1
2
3
class Light { void down() { System.out.println("灯光调暗..."); } }
class Projector { void on() { System.out.println("投影仪打开..."); } }
class Stereo { void on() { System.out.println("音响打开..."); } }

第二步:创建“外观”类 (Facade)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class HomeTheaterFacade {
private Light light = new Light();
private Projector projector = new Projector();
private Stereo stereo = new Stereo();

// 核心:提供一个简单的方法,内部封装复杂调用
public void watchMovie() {
System.out.println("--- 准备观影模式 ---");
light.down();
projector.on();
stereo.on();
}
}

第三步:客户端调用

1
2
3
4
5
6
7
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheater = new HomeTheaterFacade();
// 用户只需要点一下,不需要知道后台有多少设备
homeTheater.watchMovie();
}
}


以Shiro为例进一步说明

在 Shiro 中,SecurityManager 正是那个 “政务大厅综合窗口”。

核心痛点

Shiro 内部非常复杂,包含认证(Authenticator)、授权(Authorizer)、会话管理(SessionManager)、缓存管理(CacheManager)等。如果开发者直接操作这些组件,代码会变成:authc.authenticate(token) -> authorizer.checkRole(id) -> sessionMgr.create() … 这 TM 太痛苦了!


实现逻辑拆解

  • 内部聚合:DefaultSecurityManager 内部持有所有核心组件的引用。

  • 方法委托:当你调用 securityManager.login(token) 时,它并不亲自处理,而是偷偷传给了内部的 Authenticator。


核心源码级伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DefaultSecurityManager implements SecurityManager {
// 内部持有一堆“打工仔”组件
protected Authenticator authenticator;
protected Authorizer authorizer;
protected SessionManager sessionManager;

// 外观模式的体现:对外只露一个 login 接口
public Subject login(Subject subject, AuthenticationToken token) {
// 实际上是委托给内部的认证器去干活
AuthenticationInfo info = this.authenticator.authenticate(token);

// 然后再委托给内部的会话管理器去创建会话
Session session = this.sessionManager.start(context);

return createSubject(token, info, subject);
}

public boolean isPermitted(PrincipalCollection principals, String permission) {
// 授权请求直接委托给内部的 Authorizer
return this.authorizer.isPermitted(principals, permission);
}
}

看到了吧,作为开发者,你只需要 Subject.login(),底层换了 Realm 还是换了 RedisSessionDAO,你的业务代码一行都不用改。这就非常符合 高内聚,低耦合 的黄金设计原则。