外观模式

今天学习一个新的设计模式---外观模式,对于这个模式可能看名称不是很有印象,但实际上还是给客户端解耦,让其调用更加简单、方便,具体请看下面进行一一分解:

定义:

  为了系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(对于这个概念可能不是太好理解,先抛出理解,之后看下代码之后就能理解了,请继续往下看!)

结构及说明:

  

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

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

(注意:Facade对象知道各个模块,但是各个模块不应该知道Facade对象)

对于上面的概念可能不是那么直观清晰,下面举一个生活中的一个例子来进一步阐述Facade模式的意义。

组装电脑,我想大家都曾经有过体会,会有如下两种情况

1、自已完全组装:

说明:各个配件都得我们亲自去相应的零件公司去挑选,然后再自己组装好,对于自己来说,是一件非常非常辛苦的工作!

2、找专业装机公司组装:

说明:由于有了装机公司的加入,客户自己就完全不用管配件从哪个厂家买过来,及如何装配了,对于自己来说,既轻松,又省事!而Facade模式就是扮演"装机公司"的角色,通过这个形象的生活例子,应该就比较容易理解这个模式的定义了吧!

下面正式进入代码阶段来加深对模式的理解,以一个“代码生成的应用”来例子,来体现一下外观模式:

这个代码生成工具是由多个生成模块组成的,所以现在客户端需要使用这个代码生成工具来生成需要的基础代码,该如何实现呢?以下用两种方案来进行对比,进一步突出外观模式的作用。

不用模式的解决方案:

工程结构如下:

 

下面具体来展现各个类的代码,就知道其调用关系了:

ConfigModel【这里面主要是存放配置开关,是否要生成某个模块的代码】:

/**
 * 示意配置描述的数据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;
    }
}

 ConfigManager.java【配置管理器,主要是供模块来调用的】:

/**
 * 示意配置管理,就是负责读取配置文件, 并把配置文件的内容设置到配置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;
    }
}

以上是配置相关的类,以下则是各个生成模块的代码了,都调用了以上配置管理器:

Presentation.java:

/**
 * 示意生成表现层的模块
 */
public class Presentation {
    public void generate() {
        // 1:从配置管理里面获取相应的配置信息
        ConfigModel cm = ConfigManager.getInstance().getConfigData();
        if (cm.isNeedGenPresentation()) {
            // 2:按照要求去生成相应的代码,并保存成文件
            System.out.println("正在生成表现层代码文件");
        }
    }
}

Business.java:

/**
 * 示意生成逻辑层的模块
 */
public class Business {
    public void generate() {
        // 1:从配置管理里面获取相应的配置信息
        ConfigModel cm = ConfigManager.getInstance().getConfigData();
        if (cm.isNeedGenBusiness()) {
            // 2:按照要求去生成相应的代码,并保存成文件
            System.out.println("正在生成逻辑层代码文件");
        }
    }
}

 DAO.java:

/**
 * 示意生成数据层的模块
 */
public class DAO {
    public void generate() {
        // 1:从配置管理里面获取相应的配置信息
        ConfigModel cm = ConfigManager.getInstance().getConfigData();
        if (cm.isNeedGenDAO()) {
            // 2:按照要求去生成相应的代码,并保存成文件
            System.out.println("正在生成数据层代码文件");
        }
    }
}

这时客户端要拿生成工具开始生成代码了,代码如下:

Client.java:

public class Client {
    public static void main(String[] args) {
        new Presentation().generate();
        new Business().generate();
        new DAO().generate();
    }
}

输出结果如下:

解析:从客户端的生成代码来看,需对各个生成模块有所了解才能够生成代码,也就是与客户端耦合得太紧密了,这不是一个很好的程序!!于是请继续看下面的解决之道。

使用模式的解决方案:

工程结构如下:

其它的模块生成类都不变,只是加入了Facade类来改变了与客户端的交互方式,至于具体有啥变化,请看下面的代码展示,其实很简单:

Facade.java:

/**
 * 代码生成子系统的外观对象
 */
public class Facade {
    private Facade() {

    }

    /**
     * 将一个简单的调用代码生成的功能暴露给客户端,让它不用关心具体的细节,从而达到了解耦
     */
    public static void generate() {
        new Presentation().generate();
        new Business().generate();
        new DAO().generate();
    }
}

这回再看下客户端的调用:

Client.java:

public class Client {
    public static void main(String[] args) {
        // 直接调用一个方法,就可以生成相印的代码了,对于客户端,生成细节是透明的
        Facade.generate();
    }
}

解析:从客户端的生成代码来看,使用了Facade模式之后,封装了系统内部的细节功能,方便了客户端的调用,并且让客户端与系统之间解耦。

通过上面的对比,我想应该对于外观模式有了很直观的认识,下面对其进行一下概念上的进一步理解(虽说偏理论,但是有助于更深刻的理解它):

1、外观模式的目的:

  外观模式的目的不是给予系统添加新的功能接口(好像是增加了一个Facade类,里面暴露了一个接口),而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单的使用子系统。

2、外观模式的调用顺序示意图:

其中图中关键字“可能”说明我们可以灵活的组织我们要调用的模块,最后暴露给客户端,下面还有两个特点需要再说明下,重在体会:

a,有外观模式,但是可以不使用

  对于外观模式中的方法,客户端可以针对性的使用,不一定非要使用(有点费话呵,简单体会下既可)

b,外观提供了一种缺省的功能实现

  这是说的啥意思呢?拿我们举的代码生成的例子来说,Facade.java中的generate方法是一种给客户端提供的一种默认的代码生成规则,对于客户端而言,可以完全不用这种规则,而自己去拿到不同的生成模块去生成。

下面来对Facade模式做一下改变,让其与简单工厂进行结合,因为Facade可以实现成为interface(不要以为它只能做为具体的类哦!),具体怎么一个表现法,直接先上代码,然后再事后做说明:

说明:简单工厂在上一篇博文中已经做了详细的介绍,地址:http://www.cnblogs.com/webor2006/p/3360252.html

使用接口(facade模式+简单工厂模式)的解决方案:

工程结构如下:

下面列出变化的类:

FacadeApi.java:

/***
 * 定义一个代码生成接口
 */
public interface FacadeApi {
    public void generate();
}

Facade.java:

/**
 * 代码生成子系统的外观对象
 */
public class Facade implements FacadeApi {
    /**
     * 客户端需要的,一个简单的调用代码生成的功能
     */
    public void generate() {
        new Presentation().generate();
        new Business().generate();
        new DAO().generate();
    }
}

FacadeFactory.java:

/**
 * Facade简单工厂,用来创建具体的Facade对象
 */
public class FacadeFactory {
    private FacadeFactory() {

    }

    public static FacadeApi createFacadeApi() {
        return new Facade();
    }
}

Client.java:

public class Client {
    public static void main(String[] args) {
        // 客户端通过简单工厂来获得Facade接口,最终生成代码
        FacadeApi api = FacadeFactory.createFacadeApi();
        api.generate();
    }

}

对于上面这种面向接口的方式,用一个简单的图来描述一下:

注意:

  Facade的方法实现中,一般是负责把客户端的请求转发给子系统内部的各个模块进行处理,Facade的方法本身并不进行功能的具体实现(也就是Facade类里面并不实现客户端的具体业务逻辑,而只是把业务委派给专门模块进行处理,只是个转发的过程),Facade的方法的实现只是实现一个功能的组合调用

通过以上详细的描述,下面最后再总结一下Facade模式,结合上面的例子可以细细体会下:

Facade模式的优缺点

  1、松散耦合【这一点应该可以很清晰的理解到】

  2、简单易用

  3、更好的划分访问层次

  4、过多的或者是不太合理的Facade也容易让人迷惑【需要合理的去使用这种模式】

它的本质:

封装交互、简化调用

使用场景

     1、如果你希望为一个复杂的子系统提供一个简单接口的时候,可以考虑使用外观模式,使用外观对象来实现大部分客户需要的功能,从而简化客户的使用。

   2、如果想要让客户端和抽象类的实现部分进行松散耦合,可以考虑使用外观模式,使用外观对象来将这个子系统与它的客户端分离开来,从而提高子系统的独立性和可移植性。

     3、如果构建多层结构的系统,可以考虑使用外观模式,使用外观对象作为每层的入口,这样可以简化层与层之间的调用,也可以松散层与层之间的依赖关系。

好了,该模式就介绍到这,下个模式再见!!

posted on 2013-10-20 21:14  cexo  阅读(280)  评论(0)    收藏  举报

导航