外观模式 (Facade Pattern)
概述
外观模式是一种结构型设计模式,它为子系统中的一组接口提供一个一致的界面。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
意图
- 为子系统中的一组接口提供一个一致的界面
- 外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
适用场景
- 当你要为一个复杂子系统提供一个简单接口时
- 客户程序与抽象类的实现部分之间存在着很大的依赖性
- 当你需要构建一个层次结构的子系统时,使用外观模式定义子系统中每层的入口点
结构
┌─────────────┐ ┌─────────────┐
│ Client │──────────>│ Facade │
├─────────────┤ ├─────────────┤
│ │ │ + operation()│
└─────────────┘ └─────────────┘
▲
│
┌─────────────┐ ┌─────────────┐
│SubsystemClass1│ │SubsystemClass2│
├─────────────┤ ├─────────────┤
│ + operation()│ │ + operation()│
└─────────────┘ └─────────────┘
┌─────────────┐
│SubsystemClass3│
├─────────────┤
│ + operation()│
└─────────────┘
参与者
- Facade:知道哪些子系统类负责处理请求,将客户的请求代理给适当的子系统对象
- Subsystem Classes:实现子系统的功能,处理由Facade对象指派的任务
- Client:通过Facade接口与子系统交互
示例代码
下面是一个完整的外观模式示例,以家庭影院系统为例:
java
// Subsystem Classes - 子系统类
public class Amplifier {
private String description;
public Amplifier(String description) {
this.description = description;
}
public void on() {
System.out.println(description + " 功放打开");
}
public void off() {
System.out.println(description + " 功放关闭");
}
public void setSurroundSound() {
System.out.println(description + " 功放设置为环绕声");
}
public void setVolume(int level) {
System.out.println(description + " 功放音量设置为 " + level);
}
}
public class DvdPlayer {
private String description;
public DvdPlayer(String description) {
this.description = description;
}
public void on() {
System.out.println(description + " DVD播放器打开");
}
public void off() {
System.out.println(description + " DVD播放器关闭");
}
public void play(String movie) {
System.out.println(description + " DVD播放器正在播放 \"" + movie + "\"");
}
public void stop() {
System.out.println(description + " DVD播放器停止");
}
public void eject() {
System.out.println(description + " DVD播放器弹出");
}
}
public class Projector {
private String description;
public Projector(String description) {
this.description = description;
}
public void on() {
System.out.println(description + " 投影仪打开");
}
public void off() {
System.out.println(description + " 投影仪关闭");
}
public void wideScreenMode() {
System.out.println(description + " 投影仪设置为宽屏模式");
}
}
public class Screen {
private String description;
public Screen(String description) {
this.description = description;
}
public void up() {
System.out.println(description + " 屏幕升起");
}
public void down() {
System.out.println(description + " 屏幕降下");
}
}
public class TheaterLights {
private String description;
public TheaterLights(String description) {
this.description = description;
}
public void on() {
System.out.println(description + " 灯光打开");
}
public void off() {
System.out.println(description + " 灯光关闭");
}
public void dim(int level) {
System.out.println(description + " 灯光调暗到 " + level + "%");
}
}
public class PopcornPopper {
private String description;
public PopcornPopper(String description) {
this.description = description;
}
public void on() {
System.out.println(description + " 爆米花机打开");
}
public void off() {
System.out.println(description + " 爆米花机关闭");
}
public void pop() {
System.out.println(description + " 爆米花机正在制作爆米花");
}
}
// Facade - 外观类
public class HomeTheaterFacade {
private Amplifier amplifier;
private DvdPlayer dvdPlayer;
private Projector projector;
private Screen screen;
private TheaterLights lights;
private PopcornPopper popper;
public HomeTheaterFacade(Amplifier amplifier, DvdPlayer dvdPlayer, Projector projector,
Screen screen, TheaterLights lights, PopcornPopper popper) {
this.amplifier = amplifier;
this.dvdPlayer = dvdPlayer;
this.projector = projector;
this.screen = screen;
this.lights = lights;
this.popper = popper;
}
public void watchMovie(String movie) {
System.out.println("准备观看电影...");
popper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.wideScreenMode();
amplifier.on();
amplifier.setSurroundSound();
amplifier.setVolume(5);
dvdPlayer.on();
dvdPlayer.play(movie);
}
public void endMovie() {
System.out.println("关闭电影系统...");
popper.off();
lights.on();
screen.up();
projector.off();
amplifier.off();
dvdPlayer.stop();
dvdPlayer.eject();
dvdPlayer.off();
}
}
// Client - 客户端
public class Client {
public static void main(String[] args) {
// 创建子系统组件
Amplifier amplifier = new Amplifier("顶级");
DvdPlayer dvdPlayer = new DvdPlayer("顶级");
Projector projector = new Projector("顶级");
Screen screen = new Screen("巨大");
TheaterLights lights = new TheaterLights("影院");
PopcornPopper popper = new PopcornPopper("豪华");
// 创建外观
HomeTheaterFacade homeTheater = new HomeTheaterFacade(amplifier, dvdPlayer, projector, screen, lights, popper);
// 使用外观观看电影
homeTheater.watchMovie("复仇者联盟");
System.out.println("\n");
// 使用外观关闭电影
homeTheater.endMovie();
}
}
另一个示例 - 计算机启动
java
// Subsystem Classes - 子系统类
public class CPU {
public void freeze() {
System.out.println("CPU冻结");
}
public void jump(long position) {
System.out.println("CPU跳转到位置: " + position);
}
public void execute() {
System.out.println("CPU执行指令");
}
}
public class Memory {
public void load(long position, byte[] data) {
System.out.println("内存加载数据到位置: " + position);
}
}
public class HardDrive {
public byte[] read(long lba, int size) {
System.out.println("硬盘读取数据,LBA: " + lba + ", 大小: " + size);
return new byte[size];
}
}
public class GPU {
public void on() {
System.out.println("GPU打开");
}
public void render() {
System.out.println("GPU渲染图像");
}
}
public class SoundCard {
public void on() {
System.out.println("声卡打开");
}
public void playSound() {
System.out.println("声卡播放声音");
}
}
// Facade - 外观类
public class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
private GPU gpu;
private SoundCard soundCard;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
this.gpu = new GPU();
this.soundCard = new SoundCard();
}
public void start() {
System.out.println("计算机启动中...");
gpu.on();
soundCard.on();
cpu.freeze();
byte[] bootData = hardDrive.read(0, 1024);
memory.load(0, bootData);
cpu.jump(0);
cpu.execute();
gpu.render();
soundCard.playSound();
System.out.println("计算机启动完成");
}
public void shutdown() {
System.out.println("计算机关闭中...");
// 关闭各个组件
System.out.println("计算机关闭完成");
}
}
// Client - 客户端
public class Client {
public static void main(String[] args) {
ComputerFacade computer = new ComputerFacade();
computer.start();
System.out.println();
computer.shutdown();
}
}
优缺点
优点
- 它对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加方便
- 它实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户
- 外观模式有助于划分层次结构,对复杂的子系统提供统一的接口,降低了系统的复杂度
缺点
- 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了"开闭原则"
相关模式
- 抽象工厂模式:抽象工厂模式可以与外观模式一起使用,外观模式可以使用抽象工厂来获取子系统组件
- 中介者模式:中介者模式与外观模式都封装了对象间的交互,但中介者模式关注的是对象间的多对多交互,而外观模式关注的是简化子系统的接口
- 单例模式:外观对象通常可以设计为单例
实际应用
- Java中的javax.faces.context.FacesContext
- Java中的java.util.logging包
- Spring框架中的ApplicationContext
- Tomcat中的Request和Response对象
- 各种API的简化接口
外观模式与适配器模式的区别
- 外观模式:定义一个新的接口,简化子系统的使用,通常在系统设计阶段使用
- 适配器模式:使已有的接口能够协同工作,通常在系统维护阶段使用
外观模式关注的是简化接口,而适配器模式关注的是接口的转换。
注意事项
- 外观模式并不封装子系统,只是提供简化的接口,客户端仍然可以直接访问子系统
- 外观模式本身并不增加新的功能,只是简化了现有的功能
- 一个系统可以有多个外观类,每个外观类负责不同的功能
- 外观模式有助于分层设计,可以在每一层都定义一个外观类,简化层与层之间的交互