常用设计模式--外观模式

概述

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

飞机驾驶舱不少人都见过,当看到那些密密麻麻的按钮时,心想要是能一键启动就好了。在代码的世界里,我们也常常遇到一个业务功能需要调用很多接口甚至很多系统的情况,就像下图:

enter image description here

有时候被我们调用模块之间还需要互相调用,模块之间的关系都可以画出一张蜘蛛网。在这种情况下,要求开发者需要对每一个模块都有一定的了解,还需要了解他们之间的关系,开发一个功能的成本简直太高了,令人崩溃。

飞机驾驶舱的按钮由于某些原因不可能做成一键启动,但是我们的代码可以:

enter image description here

外观就是这个一键启动的按钮,它将多个模块或系统的代码进行了整合,而我们只要简单地调用外观暴露出来的一个接口。

这就是外观模式(也叫门面模式),其作用显而易见,就是提供一个简单接口来调用后方一群复杂的接口。

在外观模式中主要有三个角色:

  1. 子系统:已有模块或子系统,提供了一系列复杂的接口或功能
  2. 外观(门面):它了解子系统,并对外暴露一个简单的接口
  3. 客户:调用外观提供的接口来实现功能,无需了解复杂的子系统

代码示例

下面我们写一个简单的电脑启动的例子。(这个例子在许多设计模式教程中都曾出现,笔者认为这是最好的例子之一,直接借用了)

启动电脑我们通常只需要按下开机键就可以了,但电脑内部实际上启动了多个模块,如 CPU,硬盘,内存等

enter image description here

开机键就是一个很好的外观,让程序员们无需了解 CPU,内存和硬盘如何启动。

//CPU
public class CPU {
    public void start(){
        System.out.println("启动CPU");
    }
}
//硬盘
public class Disk {
    public void start(){
        System.out.println("启动硬盘");
    }
}
//内存
public class Memory {
    public void start(){
        System.out.println("启动内存");
    }
}

如果没有开机键,我们需要这么做:

new CPU().start();
new Disk().start();
new Memory().start();

有了开机键,这些操作都交给开机键去做:

//开机键
public class StartBtn {

    public void start(){
        new CPU().start();
        new Disk().start();
        new Memory().start();
    }
}

而我们只需要:

new StartBtn().start();

外观模式不仅为我们提供了一个简单方便的接口,也让我们的系统和子系统解耦。

迪米特法则(最少知道原则)

迪米特法则是说每一个类都应该尽量的少知道别的类,外观模式就是迪米特法则的应用。原本我们需要知道许多的子系统或接口,用了外观类之后,我们仅仅需要知道外观类即可。

换句话说就是:知道的太多对你没好处。

迪米特法则是希望类之间减少耦合,类越独立越好。有句话叫牵一发而动全身,如果类之间关系太紧密,与之关联的类太多,一旦你修改该类,也许会动到无数与之关联的类。

实际案例

Java 三层结构

用 Java 开发我们经常使用三层结构:

  • controller 控制器层
  • service 服务层
  • dao 数据访问层

有时候业务很简单,例如根据用户ID或者用户信息,Service 层这样写:

User getUserById(Integer id){
    return userDao.getUserById(id);
}

dao:

User getUserById(Integer id){
    //查询数据库
    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 层,就请不要跨层调用。

Tomcat中的外观模式

在做 Servlet 开发时,我们经常用的两个对象就是HttpServletRequest 和 HttpServletResponse ,但我们拿到的这两个对象其实是被 Tomcat 经过了外观包裹的对象,那么 Tomcat 为什么要这么做呢?

首先我们先来了解一下HttpServletRequest ,通过源码可以发现,HttpServletRequest 是一个接口,有两个类 RequestFacade 和 Request 实现了 HttpServletRequest :

enter image description here

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);

从这个案例中可以看出外观模式不仅仅用于将复杂的接口包装为一个简单的接口,也可以用于隐藏一些不想暴露给别人的方法或接口。

总结

外观模式主要使用场景:

  • 包装多个复杂的子系统,提供一个简单的接口
  • 重新包装系统,隐藏不想暴露的接口

优缺点

将复杂的接口简单化,减少了客户端与接口之间的耦合,提高了安全性。可能产生大量的中间类(外观类),一定程度上增加了系统的复杂度。

转载:https://gitbook.cn/gitchat/column/5b1e3647294fb04d7c22b783/topic/5b20ad6876fdce09acd9d1c2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值