【设计模式】第十篇:外观模式,开着小破车的快乐
一 开着小破车的快乐
不知道大家有没有这样开或者坐过这样一辆“小破车”,他能跑,但是内部娱乐或者说一些辅助的设备几乎可以忽略不计,条件虽然艰苦了一些,但是我们还是要自己给自己创造快乐 ,夏天太热了,先给自己安装一台空调,害,其实就是一台小电扇,接着就是我们的 360度音响体验了,其实也就是一个低音炮,来吧,最奢侈的一个设备来了,遮光板上接一个屏幕,还能连一个简单的 DVD 机器,好的吧,麻雀虽小,但是也算五脏俱全了,就像代码一样,毕竟有功能,能运行的程序就是 “好程序” 对吧哈哈哈~
(一) 小破车的辛酸
上车,打开我的小电扇,打开小音响,再放好 DVD,就可以发动小破车出发了,下车的时候熄掉火,依次关掉 DVD,音响,电扇,就可以出去了,虽然满满的仪式感,但是因为这些外接的设备都是一个一个独立的,所以不管是开启关闭,我都需要依次对其进行操作,自己忙了一天再回来在上折腾这个,别提多烦恼了
真羡慕别人的“豪华”小轿车,上车以后,一键打火,所有配套设备自动启动,凉飕飕的空调,动感的音乐
幻想着,我能不能也将我的小破车,改装成 “智能” 的模样呢?
下面我们就用代码来看一下我们的小破车设备改造
(二) 改造我的小破车
先复现一下我那些 DVD 、音响等等原来的状态
说明:这里只是为了演示,就在单线程环境下,简单的用了饿汉式单例,空调也就是上面说的小电扇,姑且这么叫好了
/**
* 空调设备
*/
public class AirConditioner {
// 饿汉式单例
private static AirConditioner instance = new AirConditioner();
public static AirConditioner getInstance() {
return instance;
}
public void turnOn() {
System.out.println("开启空调");
}
public void turnOff() {
System.out.println("关闭空调");
}
}
这是音响
/**
* 音响设备
*/
public class Sound {
// 饿汉式单例
private static Sound instance = new Sound();
public static Sound getInstance() {
return instance;
}
public void turnOn() {
System.out.println("开启音响");
}
public void turnOff() {
System.out.println("关闭音响");
}
}
这是 DVD
public class DVDPlayer {
// 饿汉式单例
private static DVDPlayer instance = new DVDPlayer();
public static DVDPlayer getInstance() {
return instance;
}
public void turnOn() {
System.out.println("开启DVD");
}
public void turnOff() {
System.out.println("关闭DVD");
}
}
如果使用传统的方式测试一下
public class Test {
public static void main(String[] args) {
// 拿到三种设备的实例
AirConditioner airConditioner = AirConditioner.getInstance();
DVDPlayer dvdPlayer = DVDPlayer.getInstance();
Sound sound = Sound.getInstance();
System.out.println("=====开启的过程=====");
airConditioner.turnOn();
dvdPlayer.turnOn();
sound.turnOn();
System.out.println("=====关闭的过程=====");
airConditioner.turnOff();
dvdPlayer.turnOff();
sound.turnOff();
}
}
测试结果
=====开启的过程=====
开启空调
开启DVD
开启音响
=====关闭的过程=====
关闭空调
关闭DVD
关闭音响
效果没问题了,但是可以看出来,只有短短三台设备的开关就需要执行 6 个方法,如果设备更多一些,如果操作不仅只有开关,还有一些别的,岂不是要累死,虽然咱的车是破旧了一些,但这也太折腾了
来吧,改造!
我们创建一个 CarFade 外观类,将这些细节内容都封装进去
public class CarFacade {
private AirConditioner airConditioner;
private DVDPlayer dvdPlayer;
private Sound sound;
// 在无参构造中拿到实例
public CarFacade() {
this.airConditioner = AirConditioner.getInstance();
this.dvdPlayer = DVDPlayer.getInstance();
this.sound = Sound.getInstance();
}
// 一键开启
public void turnOn() {
airConditioner.turnOn();
dvdPlayer.turnOn();
sound.turnOn();
}
// 一键关闭
public void turnOff() {
airConditioner.turnOff();
dvdPlayer.turnOff();
sound.turnOff();
}
}
再看看如何测试呢
package cn.ideal.facade;
/**
* @ClassName: Test
* @Author: BWH_Steven
* @Date: 2020/11/27 11:35
* @Version: 1.0
*/
public class Test {
public static void main(String[] args) {
// 拿到三种设备的实例
CarFacade carFacade = new CarFacade();
System.out.println("=====开启的过程=====");
carFacade.turnOn();
System.out.println("=====关闭的过程=====");
carFacade.turnOff();
}
}
测试结果:
=====开启的过程=====
开启空调
开启DVD
开启音响
=====关闭的过程=====
关闭空调
关闭DVD
关闭音响
效果一样没问题,但是我们作为调用者,这可舒服了,我们也可以一键开关这些娱乐辅助设备了,其实这就是利用了一种简单实用的设计模式——外观模式,下面来一起看看它的概念
二 外观模式理论
(一) 概念
外观模式(门面模式):它是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式
就着上面的例子也很好理解,空调、印象、DVD,就是一个一个复杂的子系统,而我们为这几者,提供一个一致的 CarFacade ,我们就避免去访问一个一个子系统的具体细节,而只需要执行,这个 CarFacade 提供给我们对外的一个方法,其实就是达到了一个封装,精简的效果
还有例子,例如在生活中要去办户口或者注册公司等等,我们往往需要往返于多个部门之间,到处开证明,办手续,但是如果有一个综合性质的部门,统一办理对应的业务,对于用户来说就无须来回奔走,只需要根据这个综合部分对外的窗口,提交指定的材料,等待其帮你办理即可
再回到代码上,其实我们在平时的开发中已经有意或者无意的使用到了外观模式,例如高层的模块中,我们想要调用多个相对复杂的子系统,我们为了精简接口的数量,一般都会再创建一个新的类,对其进行调用封装,然后使得最终调用者,可以更加简洁容易的调用这些子系统的功能
(二) 结构
依旧分析一下其角色:
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口或者说一致的界面,使得这些子系统更加好使用
- 子系统(Sub System)角色:实现系统的部分功能,它们其实才是我们真正想要访问的内容,客户可以通过外观角色访问它
- 客户(Client)角色:通过一个外观角色访问各个子系统的功能
(三) 优缺点
(1) 优点
- 简化了调用过程:只需要访问外观模式给出的对外接口即可完成调用
- 封装性更好:使用外观模式,子系统功能及具体细节被隐藏了起来,封装性更好
- 耦合性降低:调用者只与外观对象进行交互,不直接与子系统进行接触,降低了对子系统的依赖程序,降低了耦合
- 符合迪米特法则
(2) 缺点
- 不能很好的规避扩展风险:系统内部扩展子系统的时候,容易产生风险
- 违背开闭原则:扩展子系统的时候,可能需要修改外观类,会违背开闭原则
(四) 什么时候使用外观模式
(1) 层次复杂
我们在开发初期,会有意识的使用一些常见一些架构方式,例如 MVC 等等,在层级和业务很复杂的情况下,就需要考虑在每个层级都创建一个外观对象作为入口,这样就可以为复杂的子系统提供一些简单的接口
(2) 子系统多且复杂
我们不断的更新,扩展一些功能,就会导致有很多细小却又缺不得的类,或者有一些非常复杂的系统,例如包含很多个类,这个时候我们可以考虑创建一个外观 Facade 类,简化接口,降低它们之间的依赖
(3) 调用老旧系统功能
有一些老旧的系统,几乎已经没有维护扩展的价值对了,但是其中又有一些牵扯很大的核心功能,我们在新系统中又没有足够的精力和成本去重构,只能设计一个外观 Facade 类,来交互新旧系统的代码