1 问题提出
现在我们要组建一个家庭影院,家庭影院包括:媒体播放器、投影仪、灯光、升降式屏幕、环绕立体声、爆米花机。每个设备都有遥控器可以控制打开和关闭,我们应该如何实现才能让整个过程简单且清晰呢?
通常的方案是:将每个设备用一个类来表示,然后客户端调用各个类的实例来完成整个过程,这种方式虽然可以实现功能,但是客户端使用起来很不方便,如果能将每个设备的功能再做一层封装,对客户端只暴露特定接口,就可以简化客户端的操作复杂度,这就是外观模式。

2 外观模式
外观模式也叫做过程模式,外观模式未子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需要和这个接口发生调用,而无需关心这个子系统的内部细节。
角色分析:
1)外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当的子系统。
2)调用者(Client):外观类的使用者。
3)子系统集合:处理外观类指派的任务,它是功能的实际提供者。

3 外观模式解决影院管理问题
首先将所有的子系统(设备)的类实现出来:
媒体播放器(MediaPlayer):
public class MediaPlayer {
private static final MediaPlayer INSTANCE = new MediaPlayer();
private MediaPlayer() {
}
public static MediaPlayer getInstance() {
return INSTANCE;
}
public void turnOn() {
System.out.println("打开电影播放器");
}
public void turnOff() {
System.out.println("关闭电影播放器");
}
public void play() {
System.out.println("正在播放电影");
}
public void pause() {
System.out.println("暂停播放电影");
}
}
爆米花机(Popcorn):
public class Popcorn {
private static final Popcorn INSTANCE = new Popcorn();
private Popcorn() {
}
public static Popcorn getInstance() {
return INSTANCE;
}
public void turnOn() {
System.out.println("打开爆米花机");
}
public void turnOff() {
System.out.println("关闭爆米花机");
}
public void pop() {
System.out.println("正在出爆米花");
}
}
立体环绕音(Stereo):
public class Stereo {
private static final Stereo INSTANCE = new Stereo();
private Stereo() {
}
public static Stereo getInstance() {
return INSTANCE;
}
public void turnOn() {
System.out.println("打开立体声");
}
public void turnOff() {
System.out.println("关闭立体声");
}
public void upVoice() {
System.out.println("调高音量");
}
public void downVoice() {
System.out.println("调低音量");
}
}
升降式屏幕(Screen):
public class Screen {
private static final Screen INSTANCE = new Screen();
private Screen() {
}
public static Screen getInstance() {
return INSTANCE;
}
public void up() {
System.out.println("升起屏幕");
}
public void down() {
System.out.println("下降屏幕");
}
}
投影仪(Projector):
public class Projector {
private static final Projector INSTANCE = new Projector();
private Projector() {
}
public static Projector getInstance() {
return INSTANCE;
}
public void turnOn() {
System.out.println("打开投影仪");
}
public void turnOff() {
System.out.println("关闭投影仪");
}
public void focus() {
System.out.println("投影仪正在聚焦");
}
}
灯光(TheaterLight):
public class TheaterLight {
private static final TheaterLight INSTANCE = new TheaterLight();
private TheaterLight() {
}
public static TheaterLight getInstance() {
return INSTANCE;
}
public void turnOn() {
System.out.println("打开灯光");
}
public void turnOff() {
System.out.println("关闭灯光");
}
public void dim() {
System.out.println("调暗灯光");
}
public void bright() {
System.out.println("调亮灯光");
}
}
外观类(TheaterFacade):
public class TheaterFacade {
private MediaPlayer mediaPlayer;
private Popcorn popcorn;
private Projector projector;
private Screen screen;
private Stereo stereo;
private TheaterLight theaterLight;
public TheaterFacade() {
mediaPlayer = MediaPlayer.getInstance();
popcorn = Popcorn.getInstance();
projector = Projector.getInstance();
screen = Screen.getInstance();
stereo = Stereo.getInstance();
theaterLight = TheaterLight.getInstance();
}
public void ready() {
popcorn.turnOn();
popcorn.pop();
screen.down();
projector.turnOn();
stereo.turnOn();
mediaPlayer.turnOn();
theaterLight.dim();
}
public void play() {
mediaPlayer.play();
}
public void pause() {
mediaPlayer.pause();
}
public void end() {
theaterLight.bright();
popcorn.turnOff();
screen.up();
projector.turnOff();
stereo.turnOff();
mediaPlayer.turnOff();
}
}
客户端调用(Client):
public class Client {
public static void main(String[] args) {
TheaterFacade theaterFacade = new TheaterFacade();
// 准备工作
theaterFacade.ready();
System.out.println("-----------------------");
// 开始播放
theaterFacade.play();
System.out.println("-----------------------");
// 暂停播放
theaterFacade.pause();
System.out.println("-----------------------");
// 播放结束
theaterFacade.end();
System.out.println("-----------------------");
}
}
输出结果:
打开爆米花机
正在出爆米花
下降屏幕
打开投影仪
打开立体声
打开电影播放器
调暗灯光
-----------------------
正在播放电影
-----------------------
暂停播放电影
-----------------------
调亮灯光
关闭爆米花机
升起屏幕
关闭投影仪
关闭立体声
关闭电影播放器
-----------------------
可以看到,Client端的代码非常简单,它只与外观类TheaterFacade产生关系,对应的类图如下:

4 外观模式的特定的注意事项
1)外观模式对外屏蔽了子系统的细节,因此降低了客户端对子系统使用的复杂性。
2)外观模式将客户端与子系统的关系解耦,让子系统内部的模块更易于维护与扩展。
3)通过合理的使用外观模式,可以帮助我们更好地划分访问层次。
4)当系统需要进行分层设计时,可以考虑外观模式。
5)在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个外观类,来提供遗留系统的比较清晰简单的接口,让新系统与外观类交互,提高复用性。
6)不能过多或者不合理的使用外观模式,使用外观模式还是直接调用模块,应根据实际问题具体分析。