外观模式

用来解决上述问题的一个合理的解决方案就是外观模式。那么什么是外观模式呢?

(1)外观模式定义

 

这里先对两个词进行一下说明,一个是界面,一个是接口。

  • 界面

一提到界面,估计很多朋友的第一反应就是图形界面(GUI)。其实在这里提到的界面,主要指的是从一个组件外部来看这个组件,能够看到什么,这就是这个组件的界面,也就是所说的外观。

比如:你从一个类外部来看这个类,那么这个类的public方法就是这个类的外观,因为你从类外部来看这个类,就能看到这些。

再比如:你从一个模块外部来看这个模块,那么这个模块对外的接口就是这个模块的外观,因为你就只能看到这些接口,其它的模块内部实现的东西是被接口封装隔离了的。

  • 接口

一提到接口,做Java的朋友的第一反应就是interface。其实在这里提到的接口,主要是指的外部和内部交互的这么一个通道,通常是指的一些方法,可以是类的方法,也可以是interface的方法。也就是说,这里说的接口,并不等价于interface,也有可能是一个类。

(2)应用外观模式来解决的思路

       仔细分析上面的问题,客户端想要操作更简单点,那就根据客户端的需要来给客户端定义一个简单的接口,然后让客户端调用这个接口,剩下的事情就不用客户端管,这样客户端就变得简单了。

当然,这里所说的接口就是客户端和被访问的系统之间的一个通道,并不一定是指Java的interface。事实上,这里所说的接口,在外观模式里面,通常指的是类,这个类被称为“外观”。

外观模式就是通过引入这么一个外观类,在这个类里面定义客户端想要的简单的方法,然后在这些方法的实现里面,由外观类再去分别调用内部的多个模块来实现功能,从而让客户端变得简单,这样一来,客户端就只需要和外观类交互就可以了。

3.2.2  模式结构和说明

外观模式的结构如图3.4所示:

 

                        图3.4  外观模式结构示意图

Facade:

定义子系统的多个模块对外的高层接口,通常需要调用内部多个模块,从而把客户的请求代理给适当的子系统对象。

模块:

接受Facade对象的委派,真正实现功能,各个模块之间可能有交互。

但是请注意,Facade对象知道各个模块,但是各个模块不应该知道Facade对象。

3.2.3  外观模式示例代码

       由于外观模式的结构图过于抽象,因此把它稍稍具体点,假设子系统内有三个模块,分别是AModule、BModule和CModule,它们分别有一个示意的方法,那么此时示例的整体结构如图3.5所示:

 

图3.5  外观模式示例的整体结构示意图

还是来看看代码示例,会比较清楚。

(1)首先定义A模块的接口,A模块对外提供功能方法,从抽象的高度去看,可以是任意的功能方法,示例代码如下:

/**

 * A模块的接口

 */

public interface AModuleApi {

    /**

     * 示意方法,A模块对外的一个功能方法

     */

    public void testA();

}

(2)实现A模块的接口,  简单示范一下,示例代码如下:

public class AModuleImpl implements AModuleApi{

    public void testA() {

       System.out.println("现在在A模块里面操作testA方法");

    }

}

(3)同理定义和实现B模块、C模块,先看B模块的接口定义,示例代码如下:

public interface BModuleApi {

    public void testB();

}

    B模块的实现示意,示例代码如下:

public class BModuleImpl implements BModuleApi{

    public void testB() {

       System.out.println("现在在B模块里面操作testB方法");

    }

}

C模块的接口定义,示例代码如下:

public interface CModuleApi {

    public void testC();

}

    C模块的实现示意,示例代码如下:

public class CModuleImpl implements CModuleApi{

    public void testC() {

       System.out.println("现在在C模块里面操作testC方法");

    }

}

(4)定义外观对象,示例代码如下:

/**

 * 外观对象

 */

public class Facade {

    /**

     * 示意方法,满足客户需要的功能

     */

    public void test(){

       //在内部实现的时候,可能会调用到内部的多个模块

       AModuleApi a = new AModuleImpl();

       a.testA();

       BModuleApi b = new BModuleImpl();

       b.testB();

       CModuleApi c = new CModuleImpl();

       c.testC();

    }

}

(5)客户端如何使用呢,直接使用外观对象就可以了,示例代码如下:

public class Client {

    public static void main(String[] args) {

       //使用Facade

       new Facade().test();

    }

}

       运行结果如下:

现在在A模块里面操作testA方法

现在在B模块里面操作testB方法

现在在C模块里面操作testC方法

3.2.4  使用外观模式重写示例

       要使用外观模式重写前面的示例,其实非常简单,只要添加一个Facade的对象,然后在里面实现客户端需要的功能就可以了。

(1)新添加一个Facade对象,示例代码如下:

/**

 * 代码生成子系统的外观对象

 */

public class Facade {

    /**

     * 客户端需要的,一个简单的调用代码生成的功能

     */

    public void generate(){

       new Presentation().generate();

       new Business().generate();

       new DAO().generate();

    }

}

(2)其它的定义和实现都没有变化,这里就不去赘述了

(3)看看此时的客户端怎么实现,不再需要客户端去调用子系统内部的多个模块,直接使用外观对象就可以了,示例代码如下:

public class Client {

    public static void main(String[] args) {

        //使用Facade

       new Facade().generate();

    }

}

       去运行看看,是否能正确地实现功能。

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



(1)外观模式的目的

外观模式的目的不是给子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单的使用子系统。

这点要特别注意,因为外观是当作子系统对外的接口出现的,虽然也可以在这里定义一些子系统没有的功能,但不建议这么做。外观应该是包装已有的功能,它主要负责组合已有功能来实现客户需要,而不是添加新的实现。

(2)使用外观跟不使用相比有何变化

看到Facade的实现,可能有些朋友会说,这不就是把原来在客户端的代码搬到Facade里面了吗?没有什么大变化啊?

没错,说的很对,表面上看就是把客户端的代码搬到Facade里面了,但实质是发生了变化的,请思考:Facade到底位于何处呢?是位于客户端还是在由A、B、C模块组成的系统这边呢?

答案肯定是在系统这边,这有什么不一样吗?

当然有了,如果Facade在系统这边,那么它就相当于屏蔽了外部客户端和系统内部模块的交互,从而把A、B、C模块组合成为一个整体对外,不但方便了客户端的调用,而且封装了系统内部的细节功能,也就是说Facade与各个模块交互的过程已经是内部实现了。这样一来,如果今后调用模块的算法发生了变化,比如变化成要先调用B,然后调用A,那么只需要修改Facade的实现就可以了。

       另外一个好处,Facade的功能可以被很多个客户端调用,也就是说Facade可以实现功能的共享,也就是实现复用。同样的调用代码就只用在Facade里面写一次就好了,而不用在多个调用的地方重复写。

       还有一个潜在的好处,对使用Facade的人员来说,Facade大大节省了他们的学习成本,他们只需要了解Facade即可,无需再深入到子系统内部,去了解每个模块的细节,也不用和这多个模块交互,从而使得开发简单,学习也容易。

(3)有外观,但是可以不使用

虽然有了外观,如果有需要,外部还是可以绕开Facade,直接调用某个具体模块的接口,这样就能实现兼顾组合功能和细节功能。比如在客户端就想要使用A模块的功能,那么就不需要使用Facade,可以直接调用A模块的接口。

示例代码如下:

public class Client {

    public static void main(String[] args) {

       AModuleApi a = new AModuleImpl();

       a.testA();

    }

}

(4)外观提供了缺省的功能实现

    现在的系统是越做越大、越来越复杂,对软件的要求也就更高。为了提高系统的可重用性,通常会把一个大的系统分成很多个子系统,再把一个子系统分成很多更小的子系统,一直分下去,分到一个一个小的模块,这样一来,子系统的重用性会得到加强,也更容易对子系统进行定制和使用。

    但是这也带来一个问题,如果用户不需要对子系统进行定制,仅仅就是想要使用它们来完成一定的功能,那么使用起来会比较麻烦,需要跟这多个模块交互。

    外观对象就可以为用户提供一个简单的、缺省的实现,这个实现对大多数的用户来说都是已经足够了的。但是外观并不限制那些需要更多定制功能的用户,直接越过外观去访问内部的模块的功能。

(5)外观模式的调用顺序示意图

外观模式的调用顺序如图3.6所示:

 

图3.6  外观模式调用顺序示意图

3.3.2  外观模式的实现

(1)Facade的实现

对于一个子系统而言,外观类不需要很多,通常可以实现成为一个单例。

也可以直接把外观中的方法实现成为静态的方法,这样就可以不需要创建外观对象的实例而直接就可以调用,这种实现相当于把外观类当成一个辅助工具类实现。简要的示例代码如下:

public class Facade {

    private Facade(){     }

    public static void test(){

       AModuleApi a = new AModuleImpl();

       a.testA();

       BModuleApi b = new BModuleImpl();

       b.testB();

       CModuleApi c = new CModuleImpl();

       c.testC();

    }

}

(2)Facade可以实现成为interface

虽然Facade通常直接实现成为类,但是也可以把Facade实现成为真正的interface,只是这样会增加系统的复杂程度,因为这样会需要一个Facade的实现,还需要一个来获取Facade接口对象的工厂,此时结构如图3.7所示:

 

图3.7  外观实现成为接口的结构示意图

(3)Facade实现成为interface的附带好处

如果把Facade实现成为接口,还附带一个功能,就是能够有选择性的暴露接口方法,尽量减少模块对子系统外提供的接口方法。

换句话说,一个模块的接口里面定义的方法可以分成两部分,一部分是给子系统外部使用的,一部分是子系统内部的模块间相互调用时使用的。有了Facade接口,那么用于子系统内部的接口功能就不用暴露给子系统外部了。

比如,定义如下的A、B、C模块的接口:

public interface AModuleApi {

public void a1();

public void a2();

   

public void a3();

}

同理定义B、C模块的接口

public interface BModuleApi {

//对子系统外部

    public void b1();

    //子系统内部使用

    public void b2();

    //子系统内部使用

    public void b3();

}

public interface CModuleApi {

    //对子系统外部

    public void c1();

    //子系统内部使用

    public void c2();

    //子系统内部使用

    public void c3();

}

    定义好了各个模块的接口,接下来定义Facade的接口:

public interface FacadeApi {

public void a1();

public void b1();

public void c1();

   

   

 


public void test();

}

这样定义Facade的话,外部只需要有Facade接口,就不再需要其它的接口了,这样就能有效地屏蔽内部的细节,免得客户端去调用A模块的接口时,发现了一些不需要它知道的接口,这会造成“接口污染”。

比如a2、a3方法就不需要让客户端知道,否则既暴露了内部的细节,又让客户端迷惑。对客户端来说,他可能还要去思考a2、a3方法用来干什么呢?其实a2、a3方法是对内部模块之间交互的,原本就不是对子系统外部的,所以干脆就不要让客户端知道。

(4)Facade的方法实现

Facade的方法实现中,一般是负责把客户端的请求转发给子系统内部的各个模块进行处理,Facade的方法本身并不进行功能的处理,Facade的方法的实现只是实现一个功能的组合调用。

当然在Facade中实现一个逻辑处理也并无不可,但是不建议这样做,这不是Facade的本意,也超出了Facade的边界。

3.3.3  外观模式的优缺点

l          松散耦合
    外观模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护。

l          简单易用
    外观模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟外观交互就可以了,相当于外观类为外部客户端使用子系统提供了一站式服务。

l          更好的划分访问层次
    通过合理使用Facade,可以帮助我们更好的划分访问的层次。有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到外观中,这样既方便客户端使用,也很好的隐藏了内部的细节。

l          过多的或者是不太合理的Facade也容易让人迷惑,到底是调用Facade好呢,还是直接调用模块好。

3.3.4  思考外观模式

1:外观模式的本质

外观模式的本质:封装交互,简化调用

Facade封装了子系统外部和子系统内多个模块的交互过程,从而简化外部的调用。通过外观,子系统为外部提供一些高层的接口,以方便它们的使用。

2:对设计原则的体现

       外观模式很好的体现了“最少知识原则”。

       如果不使用外观模式,客户端通常需要和子系统内部的多个模块交互,也就是说客户端会有很多的朋友,客户端和这些模块之间都有依赖关系,任意一个模块的变动都可能会引起客户端的变动。

       使用外观模式过后,客户端只需要和外观类交互,也就是说客户端只有外观类这一个朋友,客户端就不需要去关心子系统内部模块的变动情况了,客户端只是和这个外观类有依赖关系。

这样一来,客户端不但简单,而且这个系统会更有弹性。当系统内部多个模块发生变化的时候,这个变化可以被这个外观类吸收和消化,并不需要影响到客户端,换句话说就是:可以在不影响客户端的情况下,实现系统内部的维护和扩展。

3:何时选用外观模式

建议在如下情况中,选用外观模式:

  • 如果你希望为一个复杂的子系统提供一个简单接口的时候,可以考虑使用外观模式,使用外观对象来实现大部分客户需要的功能,从而简化客户的使用
  • 如果想要让客户程序和抽象类的实现部分松散耦合,可以考虑使用外观模式,使用外观对象来将这个子系统与它的客户分离开来,从而提高子系统的独立性和可移植性
  • 如果构建多层结构的系统,可以考虑使用外观模式,使用外观对象作为每层的入口,这样可以简化层间调用,也可以松散层次之间的依赖关系

3.3.5  相关模式

l          外观模式和中介者模式
    
这两个模式非常类似,但是有本质的区别。
    中介者模式主要用来封装多个对象之间相互的交互,多用在系统内部的多个模块之间;而外观模式封装的是单向的交互,是从客户端访问系统的调用,没有从系统中来访问客户端的调用。
    在中介者模式的实现里面,是需要实现具体的交互功能的;而外观模式的实现里面,一般是组合调用或是转调内部实现的功能,通常外观模式本身并不实现这些功能。
    中介者模式的目的主要是松散多个模块之间的耦合,把这些耦合关系全部放到中介者中去实现;而外观模式的目的是简化客户端的调用,这点和中介者模式也不同。

l          外观模式和单例模式
    
通常一个子系统只需要一个外观实例,所以外观模式可以和单例模式组合使用,把Facade类实现成为单例。当然,也可以跟前面示例的那样,把外观类的构造方法私有化,然后把提供给客户端的方法实现成为静态的。

l          外观模式和抽象工厂模式
    
外观模式的外观类通常需要和系统内部的多个模块交互,每个模块一般都有自己的接口,所以在外观类的具体实现里面,需要获取这些接口,然后组合这些接口来完成客户端的功能。
    那么怎么获取这些接口呢?就可以和抽象工厂一起使用,外观类通过抽象工厂来获取所需要的接口,而抽象工厂也可以把模块内部的实现对Facade进行屏蔽,也就是说Facade也仅仅只是知道它从模块中获取的它需要的功能,模块内部的细节,Facade也不知道了。


本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。

Linux下grep的基本过滤技巧 - 2016-05-08 14:05:32

Linux下grep的基本过滤技巧 显示包含Orcl-Extract-Serv:63的字符串 tail -f /opt/apache/apache-tomcat-6.0.29/logs/catalina.out |grep Orcl-Extract-Serv:63 白名单过滤策略 显示包含Orcl-Extract-Serv:63或Orcl-Extract-Serv:62的字符串  tail -f /opt/apache/apache-tomcat-6.0.29/logs/catalina.out |gre
如何判断两个字符串是否由相同的字符组成 题目描述: 由相同的字符组成是指组成两个字符串的字母以及各个字母的个数是一样的,只是排列顺序不同而已。例如”aaaabbc”与”abcbaaa”就由相同的字符组成的。 方法一: 排序法,将两个字符串中的字符排序,比较两个排序后的字符串是否相等。若相等则表明它们是由相同的字符组成的,否则,表明他们是由不同的字符组成的。 import java.util.Arrays; public class Solution { public static void compare

自考总结之信息资源管理 - 2016-05-08 14:05:16

             继上篇 操作系统 总结之后,这篇总结也应运而生了,接下来:信息资源管理——如果CSND能加音乐的话,我点《领悟》,是有这首歌吧?自行搜索,下面直奔主题,上图一幅:         PS个人观点,欢迎大家前来交流(怎么有种结束的赶脚 )          关于《信息资源管理》说多了都是tears,这本书不仅页多而且知识点一抓一大把,各个知识点都是平行关系,感觉没有不重要的地方,其实听这书名——信息 资源 管理,就 feel 是很有含金量的知识,而且世间万物、源于一物,有着共同的“人

Windows 08R2 IIIS网站架设 - 2016-05-08 04:05:08

目录 目录 配置和安装IIS 环境设置 安装IIS服务器 网站的站点目录和欢迎页面 配置和安装IIS IIS是Windows的网站服务器,所以配置IIS服务的前提是需要一个网址、和DNS域名并添加主机记录。 环境设置 我们以下图架构搭建一个类似的IIS服务。我们将DNS服务和IIS服务ALL-IN-ONE。 关于DNS服务的配置文档, 点击这里 首先添加www.jmilk.com的解析记录 : 安装IIS服务器 Step1 :在服务器管理器中添加 Web服务器(IIS) 服务角色。在 选择角色服务 页面可
在青云上创建centos 6.4后,习惯性的安装emacs. 结果遇到报错信息: # yum install emacsLoaded plugins: fastestmirrorDetermining fastest mirrorsError: Cannot retrieve metalink for repository: epel. Please verify its path and try again 解决方法是更新ca-certificates # yum upgrade ca-certific

SharePoint 2010开发环境搭建 - 2016-05-08 04:05:56

本文的主要内容:本地电脑上搭建SharePoint 2010的开发环境,资料来源主要是翻译 微软官方文档 ,以此记录学习过程,下面就一步一步翻译重点,由于我电脑是Win 7系统,所以这里主要介绍window 7的安装环境。 当想创建SharePoint2010项目时候,通常是在已经安装过SharePoint 2010 Foundation或者是SharePoint 2010 Server的本地电脑上。用本文的说明文档来创建的开发环境不支持SharePoint Farm的安装,不能创建活跃的产品网站(You

Kafka 个人总结 - 2016-05-08 04:05:46

最近一段时间,使用Kafka比较多。从初期的FE项目中单纯调研Kafka Producer 怎么使用到后面的在弦上项目使用Kafka Consumer 期间,对Kafka有了一个大致的了解。最近由于比较空闲,所以在寻找一些关于Kafka的资料来看。总的来说,Kafka就是一个新型的分布式消息队列开源工具,现成的PDF、书籍是比较少的,最好的资料还是Apache 上的Kafka, Apache Kafka 首先说下 Kafka 的总体架构(摘抄自官网): K afka是一个生产者-消费者消息订阅系统,主载体

【C#设计模式-迭代器模式】 - 2016-05-08 04:05:46

一.概述:迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示。 二.适用性: 访问一个聚合对象的内容而无需暴露它的内部表示 支持对聚合对象的多种遍历 为遍历不同的聚合结构提供一个统一的接口 三.结构: 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口 具体迭代器角色(Concrete Iteraror):具体迭代器角色实现了迭代器接口,并需要记录遍历中的当前位置。 聚合角色(Aggregate):聚合角色负责定义获得迭代器角色的接

PAT1003我要通过!(20) - 2016-05-08 04:05:45

“答案正确”是自动判题系统给出的最令人欢喜的回复。本题属于PAT的“答案正确”大派送 —— 只要读入的字符串满足下列条件,系统就输出“答案正确”,否则输出“答案错误”。 得到“答案正确”的条件是: 字符串中必须仅有P, A, T这三种字符,不可以包含其它字符; 任意形如 xPATx 的字符串都可以获得“答案正确”,其中 x 或者是空字符串,或者是仅由字母 A 组成的字符串; 如果 aPbTc 是正确的,那么 aPbATca 也是正确的,其中 a, b, c 均或者是空字符串,或者是仅由字母 A 组成的字符
初次使用dubbo,在研发环境和测试环境测试没有问题,然后将服务上线,上线后,Dubbo服务端启动正常,客户端启动失败,并提示 Caused by: java.lang.IllegalStateException: Failed to check the status of the service com.xxx.xxx.service.LoginService. No provider available for the service com.xxxx.xxxx.service.LoginServic