1.初识外观模式
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

Facade:定义子系统的多个模块对外的高层接口,通常需要调用内部多个模块,从而把客户的请求代理给适当的子系统对象。模块:接受Facade对象的委派,真正实现功能,各个模块之间可能有交互。注意,Facade对象知道各个模块,但是各个模块不应该知道Facade对象。
2.体会外观模式
2.1 场景问题——代码生成的应用
问题如果现在客户端需要使用这个代码生成工具来生成需要的基础代码,该如何实现呢?
2.2 不用模式的解决方案
存在的问题:
客户端为了使用生成代码的功能,需要与生成代码子系统内部的多个模块交互。
2.3 使用模式的解决方案

3.理解外观模式
3.1 认识外观模式
1.外观模式的目的
外观模式的目的不是给子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单的使用子系统。
2.使用外观跟不使用相比有何变化
Facade方便了客户端的调用、封装了系统内部的细节功能、实现功能的共享和复用
3.有外观,但是可以不使用
4.外观提供了缺省的功能实现
5.外观模式的调用顺序示意图

3.2 外观模式的实现
1.把外观类当成一个辅助工具类实现
2:Facade可以实现成为interface

3.Facade实现成为interface的附带好处能够有选择性的暴露接口方法,尽量减少模块对子系统外提供的接口方法。
4.Facade的方法实现
Facade的方法实现中,一般是负责把客户端的请求转发给子系统内部的各个模块进行处理,Facade的方法本身并不进行功能的处理,Facade的方法的实现只是实现一个功能的组合调用。
3.3 外观模式的优缺点
- 松散耦合
- 简单易用
- 更好的划分访问层次
- 过多的或者是不太合理的Facade也容易让人迷惑
4.思考外观模式
4.1 外观模式的本质
外观模式的本质是:封装交互,简化调用
4.2 对设计原则的体现
体现了“最少知识原则” (迪米特原则)。
4.3 何时选用
- 1)如果你希望为一个复杂的子系统提供一个简单接口的时候,可以考虑使用外观模式,使用外观对象来实现大部分客户需要的功能,从而简化客户的使用
- 2)如果想要让客户程序和抽象类的实现部分松散耦合,可以考虑使用外观模式,使用外观对象来将这个子系统与它的客户分离开来,从而提高子系统的独立性和可移植性
- 3)如果构建多层结构的系统,可以考虑使用外观模式,使用外观对象作为每层的入口,这样可以简化层间调用,也可以松散层次之间的依赖关系
另一种总结:
- 包装多个复杂的子系统,提供一个简单的接口
- 重新包装系统,隐藏不想暴露的接口
5.案例
5.1 Java 三层结构
用 Java 开发我们经常使用三层结构:
- controller 控制器层
- service 服务层
- dao 数据访问层
有时候业务很简单,例如根据用户ID或者用户信息,Service 层这样写:
User getUserById(Integer id){
return userDao.getUserById(id);
}
dao:
User getUserById(Integer id){
//查询数据库
User user = queryDB();
return user;
}
Service 层直接调用了userDao 的 getUserById() , Service 本身并没有执行什么额外的代码,那么为什么不省去 Service 层呢?
其实三层结构也蕴含了外观模式的思想在内。假如 Service 有一个转账方法:
public boolean transMoney(Integer user1,Integer user2,Float money){
//用户1加钱
userDao.addMoney(user1,money);
//用户2扣钱
userDao.decMoney(user2,money);
//转账日志
logDao.addLog(user1,user2,money);
}
作为调用方来说,并不想知道转账操作具体要调用哪些 Dao,一行代码 transMoney() 就能搞定岂不是皆大欢喜。
因此 Service 是很有必要的,一般在业务系统中,Service 层的类不仅仅是简单的调用 dao,而是作为外观,给 Controller 提供了更方便好用的接口。
不过无论多复杂的系统,总会有 Service 直接调用 dao 的getUserById() 的情况 ,我们是否可以偷懒直接在 Controller 调用 Dao 呢?
理论上是没问题的,但是强烈建议不要这么干,因为这样会导致层侵入,三层结构的层级混乱。
除非你的业务真的简单到极致,那么干脆直接舍弃 Service 层。只要你有Service 层,就请不要跨层调用。
5.2 Tomcat中的外观模式
在做 Servlet 开发时,我们经常用的两个对象就是HttpServletRequest 和 HttpServletResponse ,但我们拿到的这两个对象其实是被 Tomcat 经过了外观包裹的对象,那么 Tomcat 为什么要这么做呢?
首先我们先来了解一下HttpServletRequest ,通过源码可以发现,HttpServletRequest 是一个接口,有两个类 RequestFacade 和 Request 实现了 HttpServletRequest :

以Facade 命名的,毫无疑问是用了外观模式,下面给出一部分源码:
Request类,继承 HttpServletRequest :
public class Request implements HttpServletRequest {
}
当我们需要使用 Request 对象时,Tomcat 传给我们的其实并不是 Request对象,而是 RequestFacade
RequestFacade 类:
package org.apache.catalina.connector;
public class RequestFacade implements HttpServletRequest {
protected Request request = null;
public RequestFacade(Request request) {
this.request = request;
}
public Object getAttribute(String name) {
if (this.request == null) {
throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
} else {
return this.request.getAttribute(name);
}
}
public String getProtocol() {
if (this.request == null) {
throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
} else {
return this.request.getProtocol();
}
}
}
从上面 RequestFacade 源码中可以看到,当调用 getAttribute() , getProtocol() 等方法时,其实还是调用了Request 对象的getAttribute() 方法。
既然如此,为什么要多次一举弄个 RequestFacade 呢 ,其实是为了安全,Tomcat 不想把过多的方法暴露给别人。
Tomcat 内部有很多组件,组件之间经常需要通讯,有些方法不得不定义为Public,这样才能被其他组件所调用。
但是有些方法只希望内部通讯用,并不想暴露给 Web 开发者,否则会有安全问题。所以定义一个外观类,只实现想要暴露给外部的方法。
所以 Tomcat 要传 Request 给我们的时候,其实是这么做的:
return new RequestFacade(request);
从这个案例中可以看出外观模式不仅仅用于将复杂的接口包装为一个简单的接口,也可以用于隐藏一些不想暴露给别人的方法或接口。