外观模式
外观模式
需求背景
外观就是这个一键启动的按钮,它将多个模块或系统的代码进行了整合,而我们只要简单地调用外观暴露出来的一个接口。这就是外观模式( 也叫门面模式 ),其作用显而易见,就是提供一个简单接口来调用后方一群复杂的接口。
模型组成
在外观模式中主要有三个角色:
- 子系统:已有模块或子系统,提供了一系列复杂的接口或功能。
- 外观( 门面 ):它了解子系统,并对外暴露一个简单的接口。
- 客户:调用外观提供的接口来实现功能,无需了解复杂的子系统。
迪米特法则
迪米特法则是说每一个类都应该尽量地少知道别的类,外观模式就是迪米特法则的应用。原本我们需要知道许多的子系统或接口,用了外观类之后,我们仅仅需要知道外观类即可。
换句话说就是:知道的太多对你没好处。
迪米特法则是希望类之间减少耦合,类越独立越好。有句话叫牵一发而动全身,如果类之间关系太紧密,与之关联的类太多,一旦你修改该类,也许会动到无数与之关联的类。
使用案例
- JAVA 三层结构
用 JAVA 开发我们经常使用三层结构:
- controller 控制器层。
- Service 服务层。
- Dao 数据访问层。
//转账
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 层,就请不要跨层调用。
- Tomcat 中的外观模式
RequestFacade 源码中可以看到,当调用 getAttribute() , getProtocol() 等方法时,其实还是调用了 Request 对象的 getAttribute() 方法。
既然如此,为什么要多此一举弄个 RequestFacade 呢 ,其实是为了安全,Tomcat 不想把过多的方法暴露给别人。
外观模式不仅仅用于将复杂的接口包装为一个简单的接口,也可以用于隐藏一些不想暴露给别人的方法或接口。
使用场景
- 包装多个复杂的子系统,提供一个简单的接口。
- 重新包装系统,隐藏不想暴露的接口。
优缺点比较
- 优点
- 将复杂的接口简单化,减少了客户端与接口之间的耦合,提高了安全性。
- 缺点
- 可能产生大量的中间类( 外观类 ),一定程度上增加了系统的复杂度。
示例
假设我们有一台计算器,在启动计算机的时候我们需要依次启动它的CPU、内存和磁盘,但是对于外部用户在使用的时候,如果我们直接暴露上述功能给用户,一个是增加了用户的学习和使用成本,二个是增大的出现错误和引起故障的风险,对于这种本就是内部业务处理的范畴,应该将其封装后暴露一个简易的入口出来,比如一个启动按钮。
下面我们通过程序模拟一下这个过程:
CPU类:
public class CPU {
public void start(){
System.out.println("启动CPU");
}
}
Memory类:
public class Memory {
public void start(){
System.out.println("启动内存");
}
}
Disk类:
public class Disk {
public void start(){
System.out.println("启动硬盘");
}
}
启动按钮类:
public class StartBtn {
public void start() {
new CPU().start();
new Disk().start();
new Memory().start();
}
}
单元测试
import com.netease.learn.designPattern.facade.StartBtn;
import org.junit.Test;
public class FacadeTest {
@Test
public void test() {
new StartBtn().start();
}
}