一、介绍
外观模式(Facade Pattern),又称为门面模式,属于结构型设计模式。外观模式为一组子系统的逻辑调用提供一个独立的接口调用。
在不使用外观模式的情况下,实现一个功能时需要按照一定的顺序对多个方法调用,因此我们需要关注对哪些方法进行调用,以及按照什么样的顺序。
而在外观模式中,我们通过调用外观类(Facade)的一个方法就可以实现该功能,该外观类(Facade)内部按照对应的顺序对这些方法进行调用,而具体按照什么样的顺序调用哪些方法我们无需关注,交给外观类(Facade)就可以了。
为子系统中的一组接口提供一个一致的界面。Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式将我们的关注点从对子系统的调用转移到外观类(Facade)上。
外观模式可以隐藏某个功能的内部逻辑细节,而提供给我们最简单的方式实现该功能。
二、一个形象的比喻
我是房地产开发商,我想盖一栋大楼,但是盖大楼需要各种各样的建筑商(搞地基的,砌墙的,搞水泥的,等等),我是高贵的开发商,我不想亲力亲为地联系这么多建筑商,怎么办呢?哎~,我找个包工头,我只和包工头对接大楼的进展,至于找哪家建筑商怎么找,这是包工头的事,I don't care,我只关注这栋大楼能盖好就行。
三、外观模式中的角色
在外观模式中,包含以下三个角色:
子系统(System)
功能的实际提供者,多个子系统共同实现一个功能。
外观类(Facade)
将多个子系统的功能进行整合,并向客户端提供实现整合后功能的唯一接口。用于隐藏功能的内部细节。
调用方(Client)
外观类的调用方,只需要关注外观类提供的功能,无需关注内部细节。
下面是外观模式的UML类图
四、案例引用
我本人从上小学至今特别喜欢吃西红柿炒鸡蛋面条,记得是小学四年级的时候,我一顿午饭吃了一大海碗,吃得干干净净一点不浪费,那就以制作一顿西红柿炒鸡蛋面条为例吧。
制作一碗西红柿炒鸡蛋面条挺简单的,我们假设现在有三个角色:
负责备菜的小绿
你负责买菜,回来还要洗菜,并切好西红柿、打好鸡蛋。
负责西红柿炒鸡蛋的小红
小绿备菜完成后,该小红上场表演了,很简单,炒鸡蛋、炒西红柿、加入调味料、加入鸡蛋、搅拌、关火。
负责煮面条的小白
小白再拿出一个汤锅,盛满水,将水烧开后加入面条,煮沸两次后捞出面条盛入碗中。
最后舀一些西红柿炒鸡蛋搅拌到面条中,一碗美味的西红柿炒鸡蛋面条就ok了。
在享用完这一顿美食后,还需要有人擦桌子,有人刷锅洗碗。现在我们把这两个工作继续分配
- 负责备菜的小绿被分配擦桌子
- 负责煮面条的小白被分配刷锅洗碗
下面我们用代码模拟这个过程。
五、不使用外观模式
在不使用外观模式的情况下,我们客户端通常会在一个方法中先获取小绿、小红、小白这三个角色的实例,然后依次调用这三个角色的方法完成响应的步骤。
1. 子系统A
在前面的介绍中,小绿负责的工作不仅有备菜,还有擦桌子。下面我们对小绿这个角色进行实现
public class XiaoLv {
/** 买菜 */
public void maiCai() {
System.out.println("小绿:买菜");
}
/** 洗菜 */
public void xiCai() {
System.out.println("小绿:洗菜");
}
/** 切西红柿 */
public void qieXiHongShi() {
System.out.println("小绿:切西红柿");
}
/** 打鸡蛋 */
public void daJiDan() {
System.out.println("小绿:打鸡蛋");
}
/** 擦桌子 */
public void caZhuoZi() {
System.out.println("小绿:擦桌子");
}
}
2. 子系统B
小绿负责的工作:炒鸡蛋、炒西红柿、加入调味料、加入鸡蛋、搅拌、关火。下面我们对小红这个角色进行实现
public class XiaoHong {
/** 炒鸡蛋 */
public void chaoJiDan() {
System.out.println("小红:炒鸡蛋");
}
/** 炒西红柿 */
public void chaoXiHongShi() {
System.out.println("小红:炒西红柿");
}
/** 添加调味料 */
public void addTiaoWeiLiao() {
System.out.println("小红:添加调味料");
}
/** 加入鸡蛋 */
public void addJiDan() {
System.out.println("小红:加入鸡蛋");
}
/** 搅拌 */
public void jiaoBan() {
System.out.println("小红:搅拌");
}
/** 关火 */
public void closeFire() {
System.out.println("小红:关火");
}
}
3. 子系统C
小白负责的工作不仅有煮面条,还有刷锅洗碗。下面我们对小白这个角色进行实现
public class XiaoBai {
/** 烧开水 */
public void shaoKaiShui() {
System.out.println("小白:烧开水");
}
/** 加入面条 */
public void addMianTiao() {
System.out.println("小白:加入面条");
}
/** 捞出面条 */
public void takeMianTiao() {
System.out.println("小白:捞出面条");
}
/** 洗碗 */
public void xiWan() {
System.out.println("小白:洗碗");
}
}
完成上面三个角色后,我们新建一个客户端类FacadeClient
来完成这一整个步骤逻辑
4. 客户端类
在客户端Client
中,要想制作一碗面条是相当麻烦的,不仅要对三个角色进行实例化,还得时刻关注每一个角色干嘛干嘛,缺少任何一个步骤都可能导致吃不上这顿饭。
public class Client {
public static void main(String[] args) {
// 获取小绿、小红、小白三个角色的实例
XiaoLv xiaoLv = new XiaoLv();
XiaoHong xiaoHong = new XiaoHong();
XiaoBai xiaoBai = new XiaoBai();
// 开始做饭
System.out.println("开始.......");
System.out.println();
xiaoLv.maiCai(); // 小绿:买菜
xiaoLv.xiCai(); // 小绿:洗菜
xiaoLv.qieXiHongShi(); // 小绿:切西红柿
xiaoLv.daJiDan(); // 小绿:打鸡蛋
System.out.println();
xiaoHong.chaoJiDan(); // 小红:炒鸡蛋
xiaoHong.chaoXiHongShi(); // 小红:炒西红柿
xiaoHong.addTiaoWeiLiao(); // 小红:加入调味料
xiaoHong.addJiDan(); // 小红:加入鸡蛋
xiaoHong.jiaoBan(); // 小红:搅拌
xiaoHong.closeFire(); // 小红:关火
System.out.println();
xiaoBai.shaoKaiShui(); // 小白:烧开水
xiaoBai.addMianTiao(); // 小白:加入面条
xiaoBai.takeMianTiao(); // 小白:捞出面条
System.out.println();
System.out.println("开始享用美食:西红柿炒鸡蛋捞面条......");
System.out.println();
xiaoLv.caZhuoZi(); // 小白:小绿:擦桌子
xiaoBai.xiWan(); // 小白:刷锅洗碗
}
}
运行客户端代码后输出如下
从结果上来看,我的功能需求实现了,做好了饭,吃饱了面,擦好了桌子。
从过程上来看,明明我不负责炒菜、煮面等操作,但我却还要花费精力在这上面,其实我根本不关心面是怎么做的,我只在乎我能不能吃到这碗面。
该示例代码的UML图如下所示
六、使用外观模式
使用外观模式时,需要新建一个外观类,将做面条这些过程细节都放在外观类里面,外观类只需要给客户端提供一个能返回结果的方法即可。现在比如我去一家河南面馆吃饭,点了一大碗西红柿炒鸡蛋面条,然后坐下等着上饭就行了,饭店可以帮我做好一切事情。
现在我们对上面不使用外观模式的代码进行改进。
1. 外观类
下面新建一个外观类河南面馆(HenanNoodlesFacade
)
public class HenanNoodlesFacade {
private final XiaoLv xiaoLv = new XiaoLv();
private final XiaoHong xiaoHong = new XiaoHong();
private final XiaoBai xiaoBai = new XiaoBai();
/** 做面条 */
public void makeNoodles() {
xiaoLv.maiCai(); // 小绿:买菜
xiaoLv.xiCai(); // 小绿:洗菜
xiaoLv.qieXiHongShi(); // 小绿:切西红柿
xiaoLv.daJiDan(); // 小绿:打鸡蛋
System.out.println();
xiaoHong.chaoJiDan(); // 小红:炒鸡蛋
xiaoHong.chaoXiHongShi(); // 小红:炒西红柿
xiaoHong.addTiaoWeiLiao(); // 小红:加入调味料
xiaoHong.addJiDan(); // 小红:加入鸡蛋
xiaoHong.jiaoBan(); // 小红:搅拌
xiaoHong.closeFire(); // 小红:关火
System.out.println();
xiaoBai.shaoKaiShui(); // 小白:烧开水
xiaoBai.addMianTiao(); // 小白:加入面条
xiaoBai.takeMianTiao(); // 小白:捞出面条
}
/** 善后工作 */
public void clean() {
xiaoLv.caZhuoZi(); // 小白:小绿:擦桌子
xiaoBai.xiWan(); // 小白:刷锅洗碗
}
}
然后我们想吃面条的时候,只需要和面馆外观类打交道就可以了
2. 客户端类
下面是我们的客户端类FacadeClient
public class FacadeClient {
public static void main(String[] args) {
HenanNoodlesFacade facade = new HenanNoodlesFacade();
System.out.println("开始.......");
System.out.println();
// 做面条
facade.makeNoodles();
System.out.println();
System.out.println("开始享用美食:西红柿炒鸡蛋捞面条......");
System.out.println();
// 善后清理工作
facade.clean();
}
}
输出结果如下所示
该示例代码的UML图如下所示
以上就是对外观模式的介绍与讲解,下面我们对外观模式的优缺点进行阐述。
七、优缺点
优点:
- 隐藏了功能细节,关注功能本身
缺点:
- 引入了多余的外观类。因为没有外观类,同样可以实现一样的结果。
纸上得来终觉浅,绝知此事要躬行。
————————我是万万岁,我们下期再见————————