java设计模式-门面模式(外观模式 Facade)

定义

门面模式是对象的结构模式,外部与一个子系统的通信必须通过一个统一的门面对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。

医院的例子

现在的软件系统都是比较复杂的,设计师处理复杂问题的一个常见方法就是将其“分而治之”,把一个系统拆分成几个较小的子系统。如果把一个医院作为一个软件系统,按照部门职能,这个系统可以划分为挂号、门诊、划价、化验、收费、取药等多个子系统。看病的病人需要同这些部门打交道,就如同一个系统的客户端与系统的各个类打交道一样,不是一件容易的事情。

首先病人必须先挂号,然后门诊。如果医生要求化验,则病人需要先划价,然后缴费,才能到化验部门进行化验检查。拿到化验结果后,再回到门诊室。

医院就诊流程

上图描述的是病人在医院里的体验,图中的方框代表医院。

解决这种不变的方法就是引进门面模式,医院可以设置一个接待员的位置,由接待员负责代为挂号、划价、缴费、取药等过程。这个接待员就是门面模式的体现,病人只需要接触接待员,由接待员与各个部门打交道。

医院门面模式体现

门面模式的结构

门面模式没有一个一般化的类图描述,最好的描述方法实际上就是以一个例子说明。

门面模式结构图

由于门面模式的结构图过于抽象,因此把它稍微具体一点。假设系统有三个模块,分别是ModuleAModuleBModuleC,他们分别有一个示例方法,那么此时示例的整体结构图如下:

门面模式具体设计图示例

在这个对象图中,出现了两个角色:

  • 门面角色(Facade):客户端可以调用这个角色的方法。此角色知晓相关的(一个或多个)子系统的功能和责任。在正常情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去。
  • 子系统角色(SubSystem):可以同时有一个或多个子系统,每个子系统都不是一个单独的类,而是一个类的集合(例如上面的子系统由三个ModuleAModuleBModuleC三个类组成)。每个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。

示例代码

子系统角色中的类

 public class ModuleA {
    public void testA() {
        System.out.println("调用了ModuleA中的testA方法。");
    }
}
 public class ModuleB {
    public void testB() {
        System.out.println("调用了ModuleB中的testB方法。");
    }
}
 public class ModuleC {
    public void testC() {
        System.out.println("调用了ModuleC中的testC方法。");
    }
}

门面角色类:

public class Facade {
    public void test() {
        ModuleA moduleA = new ModuleA();
        moduleA.testA();
        ModuleB moduleB = new ModuleB();
        moduleB.testB();
        ModuleC moduleC = new ModuleC();
        moduleC.testC();
    }
}

客户端角色类

public class Client {
    public static void main(String[] args) {
        Facade facade = new Facade();
        facade.test();
    }
}

Facade类其实相当于A、B、C模块的外观界面,有了这个Facade类,那么客户端就不需要亲自调用子系统中的A、B、C模块了,也不需要知道系统内部的实现细节,甚至都不需要知道A、B、C模块的存在,客户端只需要跟Facade类交互就好了,从而更好的实现了客户端和子系统中A、B、C模块的解耦,让客户端更容易的使用系统。


门面模式的实现

使用门面模式还有一个附带的好处,就是能够有选择性的暴露方法。一个模块中定义的方法可以分成两部分:一部分是给子系统外部使用的;一部分是子系统内部模块之间相互调用时使用的。有了Facade类,那么用于子系统内部模块之前相互调用的方法就不需要暴露给子系统外部了。

例如,定义如下A、B、C模块。

public class ModuleA {
    /**
    * 提供给子系统外部使用的方法
    */
    public void a1() {}
    
    /**
    * 子系统内部模块之间相互调用时使用的方法
    */
    public void a2() {}
    public void a3() {}
}
public class ModuleB {
    /**
    * 提供给子系统外部使用的方法
    */
    public void b1() {}
    
    /**
    * 子系统内部模块之间相互调用时使用的方法
    */
    public void b2() {}
    public void b3() {}
}
public class ModuleC {
    /**
    * 提供给子系统外部使用的方法
    */
    public void c1() {}
    
    /**
    * 子系统内部模块之间相互调用时使用的方法
    */
    public void c2() {}
    public void c3() {}
}

模块门面装饰类

public class ModuleFacade() {
    ModuleA a = new ModuleA();
    ModuleB b = new ModuleB();
    ModuleC c = new ModuleC();
    
    /**
    * 下面这些是A、B、C模块对子系统外部提供的方法
    */
    public void a1() {
        a.a1();
    }
    public void b1() {
        b.b1();
    }
    public void c1() {
        c.c1();
    }
}

这样定义一个ModuleFacade类可以有效的屏蔽内部的细节,免得客户端去调用Module的相关类时,发现一些不需要它知道的方法,比如a2()a3()方法就不需要让客户端知道,否则既暴露了内部的细节,又让客户端迷惑。对客户端来说,他可能还需要去考虑a2()a3()方法的作用。其实a2()a3()方法是内部模块之间交互的,原本就不是对子系统外部的,所以干脆就不要让客户端知道。

一个系统可以有几个门面类

在门面模式中,通常只需要一个门面类,并且此门面类只有一个实例,换句话说,它是一个单例类。当然这并不意味着在整个系统中只能有一个门面类,而仅仅是说对于一个子系统只有一个门面类。或者说,如果一个系统有好几个子系统的话,每个子系统都有一个门面类,整个系统可以有数个门面类。

为子系统增加新行为

初学者往往认为通过继承一个门面类便可以在子系统中加入新的行为,这是错误的。门面模式的用意是为子系统提供一个集中化和简化的沟通管道,而不能向子系统中增加新的行为。比如医院中的接待人员不是医护人员,接待员并不能为病人提供医疗服务。

门面模式的优点

  • 松散耦合
    门面模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易拓展和维护。
  • 简单易用
    门面模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟门面类交互即可。
  • 更好的划分访问层次
    通过合理的使用门面模式Facade,可以帮助我们更好的划分访问的层次。有些方法是对系统外部的,有些方法是对系统内部使用的。把需要暴露给外部的功能集中到门面类中,这样既方便客户端使用,也很好的隐藏了内部的细节。

门面模式在Tomcat中的使用

Tomcat中门面模式使用的很多,因为Tomcat中有很多不同组件,每个组件要相互通信,但是又不能将自己的内部数据过多的暴露给其他组件。用门面模式隔离数据是很好的方法。

下面是Request上使用的门面模式:

Request上使用的门面模式

使用过Servlet的人都清楚,除了要在web.xml做相应的配置外,还需要继承一个叫HttpServlet的抽象类,并且重写doGetdoPost方法(当然只重写service方法也是可以的)。

public class TestServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doPost(request, response);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }
}

可以看出doGetdoPost方法又两个参数,参数类型是接口HttpServletRequest与接口HttpServletResponse,那么从Tomcat中传递国来的真实类型到底是什么呢?通过Debug会发现,在真正调用TestServlet之前,会经历很多Tomcat中的方法。如下图所示:

Tomcat调试过程

注意红色方框圈中的类,StandardWrapperValue类中的invoke方法225行代码如下:

filterChain.doFilter (request.getRequest(), response.getResponse());

StandardWrapperValue类并没有直接将Request对象与Response对象传递给ApplicationFilterChain类的doFilter()方法,传递的是RequestFacadeResponseFacade对象,为什么这么说呢,看一下request.getRequest()response.getResponse()方法就真相大白了。

Request类

public HttpServletRequest getRequest() {
    if (facade == null) {
        facade = new RequestFacade(this);
    }
    return facade;
}

Response类

public HttpServletResponse getResponse() {
    if (facade == null) {
        facade = new ResponseFacade(this);
    }
    return (facade);
}

可以看到它们返回的都是各自的一个门面类,那么这样做有什么好处呢?

Request对象中的很多方法都是内部组件之间相互交互时使用的,例如setCometsetRequestedSessionId等方法(这里不一一列举)。这些方法并不对外部公开,但是又必须设置为public因为还需要跟内部组件之间交互使用。最好的解决方法就是通过使用一个Facade类,将与内部组件之间交互使用的方法屏蔽掉,只提供给外部程序感兴趣的方法。

如果不使用Facade类,直接传递的是Request对象与Response对象,那么熟悉容器内部运作的程序员可以分别把ServletRequestServletResponse对象向下转换为RequestResponse,并调用它们的公共方法,比如拥有Request对象,就可以调用setCometsetRequestedSessionId等方法,这样会危害安全性。

参考

《JAVA与模式》之门面模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 生活中的示例## 外观模式在现实生活中的示例很多,比如:组装电脑,通常会有两种方案。 一个...
    七寸知架构阅读 6,190评论 7 57
  • 本篇文章介绍一种设计模式——外观模式。本篇文章内容参考《JAVA与模式》之门面模式,外观模式,深入浅出外观模式(二...
    Ruheng阅读 7,011评论 0 8
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,894评论 1 15
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,562评论 18 399
  • 只思考本质的思考方法: 最近睡眠不好,没有锻炼,导致精神状态极其差,便导致人生这个时间段的质量非常低。当然,也体验...
    神乐酱酱酱阅读 137评论 0 0