一. 定义
在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时需要多个部门联系,这时需要一个综合部门解决一切手续问题。软件设计也是这样,当系统功能越来越强,子系统会越来越多,客户对系统的访问也变的越来越复杂,这时如果系统内部发生改变,客户端也要跟着改变,这违背开闭原则,也违背了迪米特法则,所以有必要为多个子系统提供一个统一接口,来降低系统耦合度,这就是外观模式的目标。
图 1 给出了客户去当地房产局办理房产证过户要遇到的相关部门。
外观(门面)模式(Decorator Pattern):
-
它为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,该接口使子系统更容易使用,
-
外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使调用端只与接口发生调用,无需关心子系统的内部细节;
-
在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),
我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。
尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。
二. 特点
1. 优点
-
外观模式是迪米特法则的典型应用,它降低了子系统和客户端之间的耦合度,使得子系统内部的模块更易维护和扩展;
-
对客户屏蔽子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易;
-
降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,
2. 缺点
-
不能很好地限制客户使用子系统类,很容易带来未知风险;
-
增加新的子系统可能需要修改外观类或客户端的源代码,违背开闭原则,可考虑引入抽象外观类;
三. 应用场景
-
对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可简化子系统间的依赖关系;
-
在维护一个遗留的大型系统时,可能该系统已变得非常难以维护和扩展,此时可考虑为新系统开发一个Facade类,为遗留系统提供一个比较清晰简单的接口供外界访问,让系统与Facade类交互,提高复用性;
-
不能过多的或者不合理的使用外观模式,使用外观模式还是直接调用模块要以系统有层次,好维护为依据;
四. 模式的结构
外观模式结构比较简单,主要是定义一个高层接口,它包含了对各子系统的引用,客户端可以通过它来访问各子系统功能;
1.外观模式结构图
2.外观模式角色分析
-
Facade(外观角色):外观类为调用者提供统一接口,并将调用端的请求代理给适当的子系统对象;
-
Client(调用端):外观接口的调用者,通过一个外观角色访问各个子系统的功能
-
SubSystem(子系统角色):处理Facade对象指派的任务,它是功能的实际提供者;
五. 模式的实现
需求:影院管理项目
DVD播放器,投影仪,环绕立体声、爆米花机,要求完成使用家庭影院功能,其使用过程如下
-
直接用遥控器:统筹各设备开关
-
开爆米花机
-
放下屏幕
-
开投影仪
-
开音响
-
开DVD,选dvd
-
去拿爆米花
-
调暗灯光
-
播放
-
观影结束后,关闭各种设备
1.传统方案解决影院管理
Client{
//1.创建相关对象;
//2.调用创建的各个对象的一系列方法;
//3.调用DVDPlayer,对象的play方法;
}
-
在Client中创建各个子系统的对象,并直接调用子系统(对象)相关方法,会造成调用过程的混乱,没有清晰的过程;
-
不利于在Client中,去维护对子系统的操作;
2.外观方案分析
给子系统中的一组接口提供一个一致的界面如在高层接口提供四个方法:prepare、play、pause、stop,用来访问子系统中的一群接口,即:通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节=>外观模式;
1) 实例结构图
2) 相关代码实现
//外观模式调用者
public class FacadeClient {
public static void main(String[] args) {
HomeTheatreFacade homeTheatreFacade = new HomeTheatreFacade();
homeTheatreFacade.prepare();
homeTheatreFacade.play();
homeTheatreFacade.pause();
homeTheatreFacade.stop();
}
}
//家庭影院外观类
public class HomeTheatreFacade {
//定义各个子系统的对象
private Popcorn popcorn;
private Projector projector;
private Stereo stereo;
private DVDPlayer player;
private TheatreLight theatreLight;
public HomeTheatreFacade() {
this.popcorn = Popcorn.getInstance();
this.projector = Projector.getInstance();
this.stereo = Stereo.getInstance();
this.player = DVDPlayer.getInstance();
this.theatreLight = TheatreLight.getInstance();
}
public void prepare(){
popcorn.on();
popcorn.pop();
projector.on();
stereo.on();
player.prepare();
theatreLight.on();
}
public void play(){
player.play();
}
public void pause(){
player.pause();
}
public void stop(){
popcorn.off();
projector.off();
stereo.off();
player.stop();
theatreLight.off();
}
}
//子系统-爆米花机
public class Popcorn {
//使用单例模式,使用饿汉式
private static final Popcorn instance = new Popcorn();
private Popcorn() {}
public static Popcorn getInstance() {
return instance;
}
public void on(){
System.out.println("Popcorn on");
}
public void off(){
System.out.println("Popcorn off");
}
public void pop(){
System.out.println("Popcorn pop");
}
}
//子系统-投影机
public class Projector {
//使用单例模式,使用饿汉式
private static Projector instance = new Projector();
private Projector() {}
public static Projector getInstance() {
return instance;
}
public void on(){
System.out.println("Projector on");
}
public void off(){
System.out.println("Projector off");
}
public void focus(){ System.out.println("Popcorn focus"); }
}
//子系统-立体声
public class Stereo {
//使用单例模式,使用饿汉式
private static Stereo instance = new Stereo();
private Stereo() {}
public static Stereo getInstance() {
return instance;
}
public void on(){
System.out.println("Stereo on");
}
public void off(){
System.out.println("Stereo off");
}
public void up(){
System.out.println("Stereo up");
}
public void down(){
System.out.println("Stereo down");
}
}
//子系统-DVD播放器
public class DVDPlayer {
//使用单例模式,使用饿汉式
private static final DVDPlayer instance = new DVDPlayer();
private DVDPlayer() {}
public static DVDPlayer getInstance() {
return instance;
}
public void prepare(){
System.out.println("DVD prepare");
}
public void play(){
System.out.println("DVD play");
}
public void pause(){
System.out.println("DVD pause");
}
public void stop(){
System.out.println("DVD stop");
}
}
//子系统-影院灯光
public class TheatreLight {
//使用单例模式,使用饿汉式
private static TheatreLight instance = new TheatreLight();
private TheatreLight() {}
public static TheatreLight getInstance() {
return instance;
}
public void on(){
System.out.println("TheaterLight on");
}
public void off(){
System.out.println("TheaterLight off");
}
public void dim(){
System.out.println("TheaterLight up");
}
public void bright(){ System.out.println("TheaterLight down");}
}
程序运行结果:
Popcorn on
Popcorn pop
Projector on
Stereo on
DVD prepare
TheaterLight on
DVD play
DVD pause
Popcorn off
Projector off
Stereo off
DVD stop
TheaterLight off
六. 外观模式与代理模式的区别
- 代理对象代表一个单一对象, 而外观代表一个子系统,
- 代理的客户对象无法直接访问目标对象,由代理对象提供单独目标对象的访问,外观对象提供的是对子系统各元件功能的简化的共同层次的接口调用。
- 代理是一种原来对象的代表,其他需要与这个对象交互的操作都是和这个代表交涉,而外观模式是规定出该操作的系统接口,由该接口去统一协调各元件配合该系统操作。