四、结构型模式——外观模式
一、什么是外观模式?
外观模式又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一的接口(你可以将其理解为上例中的基金,具体基金里面到底是投资股票,还是债券,还是外汇,咱们并不关注,因为这是由子系统来实现的),外部应用程序不用关心内部子系统的具体的细节,这样就大大降低了应用程序的复杂度,并提高了程序的可维护性。
还有一点需要大家知道,外观(Facade)模式是"迪米特法则"的典型应用。我们可以来看一下下面这张图。
先看上图左边部分,大的矩形表示的就是一个子系统,子系统里面有很多很多的类,对于访问者来说,只有了解了子系统里面的实之后,他才能更好的去使用子系统,这是在没有使用外观模式的情况下对子系统的一个访问,很明显,这增加了访问者访问的难度。
再来看上图右边部分,这是在使用外观模式的情况下对子系统的一个访问,此时,对于访问者来说,他并不需要去关注这个子系统里面是如何实现的,而是只需要去调用对外提供的统一的接口就可以正常的去访问它了,这样是不是就可以大大地降低了访问者使用子系统类的一个成本啊?其实,这也是外观模式的一个好处。
二、结构
理解了外观模式的概念之后,接下来,我们来看一下外观模式所包含的角色。
外观(Facade)模式包含以下主要角色:
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
这样,对于访问者来说,他并不需要去关注子系统,而只需要关注外观角色就行,因为我们到时候是要通过外观角色去使用子系统里面的那些对象的。
三、外观模式案例——智能家电控制
小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了一个智能音箱,可以通过语音直接控制这些智能家电的开启和关闭。
阅读完上面的描述,我们知道智能音箱代表的就是外观角色,客户只需要和这个智能音箱进行交互即可。
下面我们再来看一下这张类图。
从以上类图中可以看到,有一个电灯类(即Light),它里面有两个方法,一个是开启电灯(即on方法),一
个是关闭电灯(即off方法);也有一个电视类(即TV),它里面也有两个方法,一个是开启电视(即on方法),一个是关闭电视(即off方法);还有一个空调类(即AirCondition),它里面也是有两个方法,一个是开启空调(即on方法),一个是关闭空调(即off方法)。
以上类都不太重要,重要的是SmartAppliancesFacade类,它是一个外观类,它里面聚合了Light、TV、AirCondition等等这些类;除此之外,它里面还提供了一个无参构造及say方法,通过该say方法,我们就可以通过语音直接控制这些智能家电的开启和关闭了;最后,它里面还提供了两个方法,一个是用来一键开启所有智能家电的(即on方法),一个是用来一键关闭所有智能家电的(即off方法),注意了,这俩方法都是私有的,因为这俩方法都只在say方法里面被调用。
以上类图分析完了以后,接下来,我们就要开始编写代码实现以上案例了。
四、优缺点
优点:
- 降低了子系统与客户端之间的耦合度(很显然,这是由外观类来降低的),使得子系统的变化不会影响调用它的客户端
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目(如果客户直接去访问这些子系统的话,那么就可能要去访问不同的子系统里面的多个对象了,这还是比较麻烦的),并使得子系统使用起来更加容易
缺点
- 外观模式是不符合开闭原则的,所以修改起来很麻烦。
比如说子系统里面发生了一个改变的话,那么我们就得去修改该子系统了,虽然客户端我们是不需要修改的,而且可能我们还需要去修改外观类里面的代码。
五、使用场景
外观模式的使用场景,总结有三点:
- 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
这句话如何来理解呢?我想有必要给大家讲一下。它说的是如果系统有分层结构(即高层调用底层)的话,那么我们在去调用的时候,直接去依赖具体的一个公共接口即可,而不是再依赖具体的实现了,这样,可以使得后期代码的扩展性更好一些,当然了,也可以简化系统之间的依赖关系 - 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
这也说明以后我们在去设计咱们的软件时,一定要满足这一点。其实,你在去使用别人写的框架时,你会发现框架底层做了很多很多的封装,这样,你使用起该框架来就会特别特别简单了,这个就是人家写的框架的一个好处 - 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
我们使用外观模式,可以降低(或者消除)客户端和多个子系统之间的一个耦合,这样,当我们去修改客户端代码时,就不用再去修改子系统里面的代码了,因为此时客户端和子系统是没有任何关系的。同理,当我们去修改子系统里面的代码时,客户端代码也就不需要再进行修改了
六、源码中应用
接下来,我们来看一下之前学过的技术里面,哪一块用到了外观模式。
Tomcat相信大家一定都用过,我们主要是使用Tomcat作为一个web容器。使用Tomcat作为web容器时,接收浏览器发送过来的请求,Tomcat会将请求信息封装成ServletRequest对象,如下图①处对象。但是,大家想一想,ServletRequest是一个接口,它还有一个子接口HttpServletRequest,而我们知道doPost方法里面的request对象肯定是一个HttpServletRequest接口的子实现类对象,那大家又知不知道该request对象到底是哪个类的对象呢?相信大家之前在用的时候,都没有去关注过这个问题,这里我就为大家来揭晓这个问题的答案。
大家想要知道答案的话,其实也很简单,可以直接将该request对象打印出来,这样,你就可以看到它到底是哪个类的对象了。这里,我也就不卖关子了,直接告诉大家答案好了,该request对象其实是一个名为RequestFacade的类的对象,而该类就使用到了外观模式,为何这样说呢,下面我就为大家说道说道。
大家先看一下下面这张类图。
可以看到,最顶层是一个叫ServletRequest的接口,它下面有一个子接口,即HttpServletRequest,而该HttpServletRequest接口又有一个叫RequestFacade的子实现类。并且,在RequestFacade类中,还聚合了Request类的对象,注意,Request类也是HttpServletRequest接口的一个子实现类,这可从以上类图中看出。
那么问题来了,RequestFacade类到底有没有使用到外观模式呢?下面我们就来分析分析。
其实,RequestFacade类就是外观类,也即它代表的是外观角色,而Request类属于子系统角色,这样的话,我们只需要去和RequestFacade类的对象进行交互就行了,并不需要再直接去和Request类的对象进行交互了,因此,RequestFacade类确实是使用到了外观模式。
好,问题又来了,为什么在此处使用外观模式呢? 下面我们接着来分析。
定义RequestFacade类,让其分别实现ServletRequest和HttpServletRequest这俩接口,同时又在其里面定义了一个私有成员变量(Request类型的变量),很显然,RequestFacade类里面方法的实现会调用Request成员变量里面的方法,也就是说RequestFacade类里面的方法其本质上还是使用Request类中的方法。然后,我们在去使用的时候,Tomcat会将RequestFacade对象上转为ServletRequest(或者HttpServletRequest)并传给Servlet里面的service方法,这样即使在Servlet中被下转为RequestFacade,咱们也不能访问私有成员变量对象(即Request对象)中的方法,因为该私有成员变量对象(即Request对象)是受保护的,是不能直接去使用的。
可见,现在是既用了Request对象里面的方法,又能防止其中方法被外界不合理的访问(这是因为Request对象里面的一些方法是想对外进行一个屏蔽的)。
参考:
【1】https://www.bilibili.com/video/BV1Np4y1z7BU?p=81&spm_id_from=pageDriver&vd_source=fb823e821c48529140442158eafe1273
【2】从零开始学习Java设计模式 | 结构型模式篇:外观模式_李阿昀的博客-CSDN博客https://liayun.blog.csdn.net/article/details/119795115