寂然解读设计模式 - 外观模式

I walk very slowly, but I never walk backwards

设计模式 - 外观模式


寂然

大家好,我是寂然,本节课,我们来聊设计模式中的外观模式,老规矩,首先我们先通过一个案例需求来引入

案例演示 - 影院管理

现在有一个需求,完成使用家庭影院的功能,其过程为:

1.直接用遥控器:统筹各设备开关 6.开播放器,选喜欢的电影
2.开爆米花机 7.去拿爆米花
3.放下屏幕 8.调暗灯光
4.开投影仪 9.开始播放
5.开音响 10.观影结束后,关闭各种设备

解决方案一:一般方式

OK,拿到需求以后,我们先不考虑今天要聊的外观模式,我们用最容易想到的方法,进行解决,首先想要完成使用家庭影院的功能,首先我们要组建家庭影院, 播放器、投影仪、音响、爆米花机,等等...那每一个设备都是一个类,定义好每个设备的使用方法后,接着我们通过一个客户端 Client 去根据流程调用,来完成使用家庭影院的功能,类图如下所示


寂然解读设计模式 - 外观模式


方案分析

可以看到,我们在客户端中直接依赖各个设备,根据流程调用对象的一系列方法,就可以完成需求,是OK的,但是同样,将来 Client 端操作各个设备的时候,这个流程非常繁琐,而且只要有一个设备发生改变,那客户端的代码就要相应进行调整,所以这种方案有几个地方是可以进行优化的


那我们来考虑解决思路,可以定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法 ready,play,pause,end ),用来访问子系统中的一群接口,也就是说,就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节,其实这种思路就是外观模式,下面,我们一起来看一下外观模式的基本介绍

基本介绍

外观模式(Facade Pattern),也叫"门面模式",外观模式为多个复杂的子系统提供一个一致的界面

即通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问

外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节

原理类图

外观模式的原理类图如下图所示


寂然解读设计模式 - 外观模式


外观模式角色分析

外观类(Facade):为调用端提供统一的调用接口,外观类将调用端的请求代理给适当的子系统对象

调用者(Client):外观接口的调用者

子系统:处理Facade对象指派的任务,是功能的实际提供者

解决方案二:外观模式

原理类图

既然我们明确了,外观模式的核心思想是提供一个统一的调用接口,那首先我们来看,使用外观模式的原理类图


寂然解读设计模式 - 外观模式


代码演示

下面我们完成外观模式重构案例需求的代码

//播放器
public class Player {
​
 //由于都是设备,所以我们返回全局唯一对象
 //使用饿汉式单例模式
 private static Player player = new Player();
​
 private Player(){}
​
 public static Player getInstance(){
​
 return player;
 }
​
 public void on(){
 System.out.println("打开播放器");
 }
​
 public void off(){
 System.out.println("关闭播放器");
 }
​
 public void play(){
 System.out.println("使用播放器播放电影");
 }
​
 public void pause(){
 System.out.println("暂停播放器");
 }
​
 public void select(){
 System.out.println("选择喜欢的电影");
 }
}
​
//投影仪
public class Projector {
​
 private static Projector projector = new Projector();
​
 private Projector(){}
​
 public static Projector getInstance(){
​
 return projector;
 }
​
 public void on(){
 System.out.println("打开投影仪");
 }
​
 public void off(){
 System.out.println("关闭投影仪");
 }
​
 public void focus(){
 System.out.println("调节投影仪焦距");
 }
​
 public void zoom(){
 System.out.println("投影仪变焦");
 }
​
}
​
//音响
public class Stereo {
​
 private static Stereo stereo = new Stereo();
​
 private Stereo(){}
​
 public static Stereo getInstance(){
 return stereo;
 }
​
 public void on(){
 System.out.println("打开音响");
 }
​
 public void off(){
 System.out.println("关闭音响");
 }
​
 public void setVolume(){
 System.out.println("调节音响音量");
 }
​
}
​
//爆米花机
public class Popcorn {
​
 private static Popcorn popcorn = new Popcorn();
​
 private Popcorn(){}
​
 public static Popcorn getInstance(){
 return popcorn;
 }
​
 public void on(){
 System.out.println("打开爆米花机");
 }
​
 public void off(){
 System.out.println("关闭爆米花机");
 }
​
 public void pop(){
 System.out.println("制作爆米花");
 }
}
​
//外观类
public class HomeTheaterFacade {
​
 //聚合各个子系统
 private Player player;
​
 private Popcorn popcorn;
​
 private Projector projector;
​
 private Stereo stereo;
​
 //构造器里初始化各个子系统
 public HomeTheaterFacade() {
 this.player = Player.getInstance();
 this.popcorn = Popcorn.getInstance();
 this.projector = Projector.getInstance();
 this.stereo = Stereo.getInstance();
 }
​
 //把操作分成四步 准备
 public void ready(){
 popcorn.on();
 popcorn.pop();
 player.on();
 projector.on();
 stereo.on();
 }
​
 public void play(){
 player.select();
 projector.focus();
 player.play();
 stereo.setVolume();
 }
​
 public void pause(){
 player.pause();
 }
​
 public void end(){
 player.off();
 projector.off();
 stereo.off();
 popcorn.off();
 }
​
}
​
//客户端
public class Client {
​
 public static void main(String[] args) {
​
 HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
​
 homeTheaterFacade.ready();
 homeTheaterFacade.play();
 homeTheaterFacade.pause();
 homeTheaterFacade.end();
 }
}

相对于一般实现方式,我们可以看到,外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用,至于子系统之间的互相调用,由它们自己完成即可,这里不做演示

模式总结

优势
  • 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性

  • 外观模式可以对客户端与子系统的耦合关系进行解耦,让子系统内部的模块更易维护和扩展

  • 通过合理的使用外观模式,可以帮我们更好的划分访问的层次

(不要只是流程式)

注意事项

不要过多的或者不合理的使用外观模式,要考虑是使用外观模式好,还是直接调用模块好,我们的目的,是希望系统有层次,利于维护,利于扩展

使用场景
  • 在设计初期阶段,应该要有意识的将不同的两个层分离,比如经典的三层架构,就需要考虑在三层的层与层之间建立外观Facade(外观),这样可以为复杂的子系统提供应该简单的接口,使得耦合大大降低

  • 其次,在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,大多数的模式使用时也都会产生很多很小的类,这本是好事,但也给外部调用它们的用户程序带来使用上的困难,增加外观Facade可以提供一个简单接口,减少它们之间的依赖

  • 在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,但因为它包含非常重要的功能,新的需求开发必须依赖它,此时用外观模式Facade也是非常合适的,你可以为新系统开发一个外观Facade类,给设计粗糙或高度复杂的遗留代码提供比较清晰简单的接口,让新系统与Facade对象进行交互,Facade完成与遗留代码交互所有复杂的工作

Mybatis源码分析

流程分析

在 Mybatis 框架中,就由使用到外观模式,下面我们一起来进行分析,并画出相应的类图


寂然解读设计模式 - 外观模式


我们再来看里面的 Configuration 的 newMetaObject() 方法,其实调用的是 MetaObject 的 forObject,然后将这三个工厂对象实例传入


寂然解读设计模式 - 外观模式


我们来看 forObject 的具体实现,根据 object 的状态,来进行不同的处理


寂然解读设计模式 - 外观模式


我们进到 MetaObject 的构造器

发现仍然,根据 object 类型的不同,去使用不同的 factory 对象进行相应的处理,返回不同的 MetaObject的实例


寂然解读设计模式 - 外观模式


类图分析

那下面我们通过类图来梳理这几个类的关系


寂然解读设计模式 - 外观模式


Configuration 可以被认为扮演着外观类的角色,而 DefaultObjectFactory 等就是外观类下面的各个子系统

下节预告

OK,到这里,外观模式的相关内容就结束了,下一节,我们开启享元模式的学习,最后,希望大家在学习的过程中,能够感觉到设计模式的有趣之处,高效而愉快的学习,那我们下期见~

上一篇:寂然解读设计模式 - 模板模式


下一篇:寂然解读设计模式 - 享元模式