【设计模式】第十篇:外观模式,开着小破车的快乐

不知道大家有没有这样开或者坐过这样一辆“小破车”,他能跑,但是内部娱乐或者说一些辅助的设备几乎可以忽略不计,条件虽然艰苦了一些,但是我们还是要自己给自己创造快乐 ,夏天太热了,先给自己安装一台空调,害,其实就是一台小电扇,接着就是我们的 360度音响体验了,其实也就是一个低音炮,来吧,最奢侈的一个设备来了,遮光板上接一个屏幕,还能连一个简单的 DVD 机器,好的吧,麻雀虽小,但是也算五脏俱全了,就像代码一样,毕竟有功能,能运行的程序就是 “好程序” 对吧哈哈哈~

上车,打开我的小电扇,打开小音响,再放好 DVD,就可以发动小破车出发了,下车的时候熄掉火,依次关掉 DVD,音响,电扇,就可以出去了,虽然满满的仪式感,但是因为这些外接的设备都是一个一个独立的,所以不管是开启关闭,我都需要依次对其进行操作,自己忙了一天再回来在上折腾这个,别提多烦恼了

真羡慕别人的“豪华”小轿车,上车以后,一键打火,所有配套设备自动启动,凉飕飕的空调,动感的音乐

幻想着,我能不能也将我的小破车,改装成 “智能” 的模样呢?

下面我们就用代码来看一下我们的小破车设备改造

先复现一下我那些 DVD 、音响等等原来的状态

说明:这里只是为了演示,就在单线程环境下,简单的用了饿汉式单例,空调也就是上面说的小电扇,姑且这么叫好了

  1. /**
  2. * 空调设备
  3. */
  4. public class AirConditioner {
  5. // 饿汉式单例
  6. private static AirConditioner instance = new AirConditioner();
  7. public static AirConditioner getInstance() {
  8. return instance;
  9. }
  10. public void turnOn() {
  11. System.out.println("开启空调");
  12. }
  13. public void turnOff() {
  14. System.out.println("关闭空调");
  15. }
  16. }

这是音响

  1. /**
  2. * 音响设备
  3. */
  4. public class Sound {
  5. // 饿汉式单例
  6. private static Sound instance = new Sound();
  7. public static Sound getInstance() {
  8. return instance;
  9. }
  10. public void turnOn() {
  11. System.out.println("开启音响");
  12. }
  13. public void turnOff() {
  14. System.out.println("关闭音响");
  15. }
  16. }

这是 DVD

  1. public class DVDPlayer {
  2. // 饿汉式单例
  3. private static DVDPlayer instance = new DVDPlayer();
  4. public static DVDPlayer getInstance() {
  5. return instance;
  6. }
  7. public void turnOn() {
  8. System.out.println("开启DVD");
  9. }
  10. public void turnOff() {
  11. System.out.println("关闭DVD");
  12. }
  13. }

如果使用传统的方式测试一下

  1. public class Test {
  2. public static void main(String[] args) {
  3. // 拿到三种设备的实例
  4. AirConditioner airConditioner = AirConditioner.getInstance();
  5. DVDPlayer dvdPlayer = DVDPlayer.getInstance();
  6. Sound sound = Sound.getInstance();
  7. System.out.println("=====开启的过程=====");
  8. airConditioner.turnOn();
  9. dvdPlayer.turnOn();
  10. sound.turnOn();
  11. System.out.println("=====关闭的过程=====");
  12. airConditioner.turnOff();
  13. dvdPlayer.turnOff();
  14. sound.turnOff();
  15. }
  16. }

测试结果

  1. =====开启的过程=====
  2. 开启空调
  3. 开启DVD
  4. 开启音响
  5. =====关闭的过程=====
  6. 关闭空调
  7. 关闭DVD
  8. 关闭音响

效果没问题了,但是可以看出来,只有短短三台设备的开关就需要执行 6 个方法,如果设备更多一些,如果操作不仅只有开关,还有一些别的,岂不是要累死,虽然咱的车是破旧了一些,但这也太折腾了

来吧,改造!

我们创建一个 CarFade 外观类,将这些细节内容都封装进去

  1. public class CarFacade {
  2. private AirConditioner airConditioner;
  3. private DVDPlayer dvdPlayer;
  4. private Sound sound;
  5. // 在无参构造中拿到实例
  6. public CarFacade() {
  7. this.airConditioner = AirConditioner.getInstance();
  8. this.dvdPlayer = DVDPlayer.getInstance();
  9. this.sound = Sound.getInstance();
  10. }
  11. // 一键开启
  12. public void turnOn() {
  13. airConditioner.turnOn();
  14. dvdPlayer.turnOn();
  15. sound.turnOn();
  16. }
  17. // 一键关闭
  18. public void turnOff() {
  19. airConditioner.turnOff();
  20. dvdPlayer.turnOff();
  21. sound.turnOff();
  22. }
  23. }

再看看如何测试呢

  1. package cn.ideal.facade;
  2. /**
  3. * @ClassName: Test
  4. * @Author: BWH_Steven
  5. * @Date: 2020/11/27 11:35
  6. * @Version: 1.0
  7. */
  8. public class Test {
  9. public static void main(String[] args) {
  10. // 拿到三种设备的实例
  11. CarFacade carFacade = new CarFacade();
  12. System.out.println("=====开启的过程=====");
  13. carFacade.turnOn();
  14. System.out.println("=====关闭的过程=====");
  15. carFacade.turnOff();
  16. }
  17. }

测试结果:

  1. =====开启的过程=====
  2. 开启空调
  3. 开启DVD
  4. 开启音响
  5. =====关闭的过程=====
  6. 关闭空调
  7. 关闭DVD
  8. 关闭音响

效果一样没问题,但是我们作为调用者,这可舒服了,我们也可以一键开关这些娱乐辅助设备了,其实这就是利用了一种简单实用的设计模式——外观模式,下面来一起看看它的概念

外观模式(门面模式):它是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式

就着上面的例子也很好理解,空调、印象、DVD,就是一个一个复杂的子系统,而我们为这几者,提供一个一致的 CarFacade ,我们就避免去访问一个一个子系统的具体细节,而只需要执行,这个 CarFacade 提供给我们对外的一个方法,其实就是达到了一个封装,精简的效果

还有例子,例如在生活中要去办户口或者注册公司等等,我们往往需要往返于多个部门之间,到处开证明,办手续,但是如果有一个综合性质的部门,统一办理对应的业务,对于用户来说就无须来回奔走,只需要根据这个综合部分对外的窗口,提交指定的材料,等待其帮你办理即可

再回到代码上,其实我们在平时的开发中已经有意或者无意的使用到了外观模式,例如高层的模块中,我们想要调用多个相对复杂的子系统,我们为了精简接口的数量,一般都会再创建一个新的类,对其进行调用封装,然后使得最终调用者,可以更加简洁容易的调用这些子系统的功能

依旧分析一下其角色:

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口或者说一致的界面,使得这些子系统更加好使用
  • 子系统(Sub System)角色:实现系统的部分功能,它们其实才是我们真正想要访问的内容,客户可以通过外观角色访问它
  • 客户(Client)角色:通过一个外观角色访问各个子系统的功能
  • 简化了调用过程:只需要访问外观模式给出的对外接口即可完成调用
  • 封装性更好:使用外观模式,子系统功能及具体细节被隐藏了起来,封装性更好
  • 耦合性降低:调用者只与外观对象进行交互,不直接与子系统进行接触,降低了对子系统的依赖程序,降低了耦合
  • 符合迪米特法则
  • 不能很好的规避扩展风险:系统内部扩展子系统的时候,容易产生风险
  • 违背开闭原则:扩展子系统的时候,可能需要修改外观类,会违背开闭原则

我们在开发初期,会有意识的使用一些常见一些架构方式,例如 MVC 等等,在层级和业务很复杂的情况下,就需要考虑在每个层级都创建一个外观对象作为入口,这样就可以为复杂的子系统提供一些简单的接口

我们不断的更新,扩展一些功能,就会导致有很多细小却又缺不得的类,或者有一些非常复杂的系统,例如包含很多个类,这个时候我们可以考虑创建一个外观 Facade 类,简化接口,降低它们之间的依赖

有一些老旧的系统,几乎已经没有维护扩展的价值对了,但是其中又有一些牵扯很大的核心功能,我们在新系统中又没有足够的精力和成本去重构,只能设计一个外观 Facade 类,来交互新旧系统的代码

版权声明:本文为ideal-20原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ideal-20/p/14162716.html