3.1 场景问题
3.1.1 生活中的示例
外观模式在现实生活中的示例很多,比如:组装电脑,通常会有两种方案。
一个方案是去电子市场把自己需要的配件都买回来,然后自己组装,绝对DIY(Do It Yourself)。这个方案好是好,但是需要对各种配件都要比较熟悉,这样才能选择最合适的配件,而且还要考虑配件之间的兼容性。如图3.1所示:
图3.1 客户完全自己组装电脑
另外一个方案,就是到电子市场,找一家专业装机的公司,把具体的要求一讲,然后就等着拿电脑就好了。当然价格会比自己全部DIY贵一些,但综合起来这还算是个不错的选择,估计也是大多数人的选择。如图3.2所示:
图3.2 找专业的装机公司组装电脑
这个专业的装机公司就相当于本章的主角——Facade。有了它,我们就不用自己去跟众多卖配件的公司打交道,只需要跟装机公司交互就可以了,由装机公司去跟各个卖配件的公司交互,并组装好电脑返回给我们。
把上面的过程抽象一下,如果把电子市场看成是一个系统,而各个卖配件的公司看成是模块的话,就类似于出现了这样一种情况:客户端为了完成某个功能,需要去调用某个系统中的多个模块,把它们称为是A模块、B模块和C模块吧,对于客户端而言,那就需要知道A、B、C这三个模块的功能,还需要知道如何组合这多个模块提供的功能来实现自己所需要的功能,非常麻烦。
要是有一个简单的方式能让客户端去实现相同的功能多好啊,这样,客户端就不用跟系统中的多个模块交互,而且客户端也不需要知道那么多模块的细节功能了,实现这个功能的就是Facade。
3.1.2 代码生成的应用
考虑这样一个实际的应用:代码生成。
很多公司都有这样的应用工具,能根据配置生成代码。一般这种工具都是公司内部使用,较为专有的工具,生成的多是按照公司的开发结构来实现的常见的基础功能,比如增删改查。这样每次在开发实际应用的时候,就可以以很快的速度把基本的增删改查实现出来,然后把主要的精力都放在业务功能的实现上。
当然这里不可能去实现一个这样的代码生成工具,那需要整本书的内容,这里仅用它来说明外观模式。
假设使用代码生成出来的每个模块都具有基本的三层架构,分为:表现层、逻辑层和数据层,那么代码生成工具里面就应该有相应的代码生成处理模块。
另外,代码生成工具自身还需要一个配置管理的模块,通过配置来告诉代码生成工具,每个模块究竟需要生成哪些层的代码,比如:通过配置来描述,是只需要生成表现层代码呢,还是三层都生成。具体的模块示意如图3.3所示;
图3.3 代码生成工具的模块示意图
那么现在客户端需要使用这个代码生成工具来生成需要的基础代码,该如何实现呢?
3.1.3 不用模式的解决方案
有朋友会想,开发一个这样的工具或许会比较麻烦,但是使用一下,应该不难吧,直接调用不就可以了。
在示范客户端之前,先来把工具模拟示范出来,为了简单,每个模块就写一个类,而且每个类只是实现一个功能,仅仅做一个示范。
(1)先看看描述配置的数据Model,示例代码如下:
/** * 示意配置描述的数据Model,真实的配置数据会很多 */ public class ConfigModel { /** * 是否需要生成表现层,默认是true */ private boolean needGenPresentation = true; /** * 是否需要生成逻辑层,默认是true */ private boolean needGenBusiness = true; /** * 是否需要生成DAO,默认是true */ private boolean needGenDAO = true; public boolean isNeedGenPresentation() { return needGenPresentation; } public void setNeedGenPresentation( boolean needGenPresentation) { this.needGenPresentation = needGenPresentation; } public boolean isNeedGenBusiness() { return needGenBusiness; } public void setNeedGenBusiness(boolean needGenBusiness) { this.needGenBusiness = needGenBusiness; } public boolean isNeedGenDAO() { return needGenDAO; } public void setNeedGenDAO(boolean needGenDAO) { this.needGenDAO = needGenDAO; } } |
(2)接下来看看配置管理的实现示意,示例代码如下:
/** * 示意配置管理,就是负责读取配置文件, * 并把配置文件的内容设置到配置Model中去,是个单例 */ public class ConfigManager { private static ConfigManager manager = null; private static ConfigModel cm = null; private ConfigManager(){ // } public static ConfigManager getInstance(){ if(manager == null){ manager = new ConfigManager(); cm = new ConfigModel(); //读取配置文件,把值设置到ConfigModel中去,这里省略了 } return manager; } /** * 获取配置的数据 * @return 配置的数据 */ public ConfigModel getConfigData(){ return cm; } } |
(3)接下来就来看看各个生成代码的模块,在示意中,它们的实现类似,就是获取配置文件的内容,然后按照配置来生成相应的代码。分别看看它们的示意实现,先看生成表现层的示意实现,示例代码如下:
/** * 示意生成表现层的模块 */ public class Presentation { public void generate(){ //1:从配置管理里面获取相应的配置信息 ConfigModel cm = ConfigManager.getInstance().getConfigData(); if(cm.isNeedGenPresentation()){ //2:按照要求去生成相应的代码,并保存成文件 System.out.println("正在生成表现层代码文件"); } } } |
生成逻辑层的示意实现,示例代码如下:
/** * 示意生成逻辑层的模块 */ public class Business { public void generate(){ ConfigModel cm = ConfigManager.getInstance().getConfigData(); if(cm.isNeedGenBusiness()){ System.out.println("正在生成逻辑层代码文件"); } } } |
生成数据层的示意实现,示例代码如下:
/** * 示意生成数据层的模块 */ public class DAO { public void generate(){ ConfigModel cm = ConfigManager.getInstance().getConfigData(); if(cm.isNeedGenDAO()){ System.out.println("正在生成数据层代码文件"); } } } |
(4)此时的客户端实现,就应该自行去调用这多个模块了,示例代码如下:
public class Client { public static void main(String[] args) { //现在没有配置文件,就直接使用默认的配置,通常情况下,三层都应该生成, //也就是说客户端必须对这些模块都有了解,才能够正确使用它们 new Presentation().generate(); new Business().generate(); new DAO().generate(); } } |
运行结果如下:
正在生成表现层代码文件 正在生成逻辑层代码文件 正在生成数据层代码文件 |
3.1.4 有何问题
仔细查看上面的实现,会发现其中有一个问题:那就是客户端为了使用生成代码的功能,需要与生成代码子系统内部的多个模块交互。
这对于客户端而言,是个麻烦,使得客户端不能简单的使用生成代码的功能。而且,如果其中的某个模块发生了变化,还可能会引起客户端也需要跟着变化。
那么如何实现,才能让子系统外部的客户端在使用子系统的时候,既能简单的使用这些子系统内部的模块功能,而又不用客户端去与子系统内的多个模块交互呢?