文档章节

从源码角度理解Java设计模式——门面模式

我叫刘半仙
 我叫刘半仙
发布于 2018/12/13 22:35
字数 1353
阅读 45
收藏 1

一、门面模式介绍

门面模式定义:也叫外观模式,定义了一个访问子系统的接口,除了这个接口以外,不允许其他访问子系统的行为发生。

适用场景:子系统很复杂时,增加一个接口供外部访问。

优点:简化层级间的调用,减少依赖,防止风险。

缺点:如果设计不当,增加新的子系统可能需要修改门面类的源代码,违背了开闭原则。

类型:结构型。

类图:

二、门面模式简单实例

小明想开一个餐馆,要去政府部门办理卫生许可证、办理税务登记和办理工商登记,以前小明要一一亲自去办理,这不是一件容易的事。

政府最近简化了政务办理流程,只用小明访问一次简化政务办理的门面就可以办理到全部证件。

interface Executive{
	void approve();
}
class HealthOffice implements Executive{
    @Override
    public void approve() {
        System.out.println("卫生局通过审批");
    }  
}
class RevenueOffice implements Executive{
    @Override
    public void approve() {
        System.out.println("税务局完成登记");
    }  
}
class SaicOffice implements Executive{
    @Override
    public void approve() {
        System.out.println("工商局办理营业执照");
    } 
}
//简化政务办理流程的门面
class ApproveFacade {
    public void wholeApprove() {
        new HealthOffice().approve();
        new RevenueOffice().approve();
        new SaicOffice().approve();
    }
}

测试一下:

public class FacadeTest {

    public static void main(String[] args) {
        ApproveFacade af = new ApproveFacade();
        //一次调用门面,全部办理
        af.wholeApprove();
    }

}

三、日志门面

阿里巴巴开发手册中有这样一条规定:

其中Log4j、Logback都是日志框架,它们都有着自己的独立的Api接口。如果单独使用某个框架,会大大增加系统的耦合性。而SLF4J并不是真正的日志框架,它有一套通用的API接口。

所以阿里开发手册中直接强制用SLF4J日志门面,日志门面是门面模式的一个典型应用。SLF4J的helloworld如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

进入info方法:

上图的SubstituteLogger.class里还是调用Logger接口的info方法,NOPLogger如同它的名字一样:什么都不做,所以只有在系统引入Logback这个日志框架时,才有了Logger真正的实现类。那Log4j、Logback等日志框架是怎么和SLF4J对接的?

任何日志框架,一定都是通过自己的StaticLoggerBinder类来和SLF4J对接:

package org.slf4j.impl

import org.slf4j.ILoggerFactory;

public class StaticLoggerBinder {
 
  private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
  
  //单利模式
  public static final StaticLoggerBinder getSingleton() {
    return SINGLETON;
  }  

  //声明这个实现编译的SLF4J API的版本,这个字段的值通常随每个版本而修改
  public static String REQUESTED_API_VERSION = "1.6.99";  // !final
  
  private StaticLoggerBinder() {
    throw new UnsupportedOperationException("This code should have never made it into slf4j-api.jar");
  }

  public ILoggerFactory getLoggerFactory() {
    throw new UnsupportedOperationException("This code should never make it into slf4j-api.jar");
  }

  public String getLoggerFactoryClassStr() {
    throw new UnsupportedOperationException("This code should never make it into slf4j-api.jar");
  }
}

这个类的实现,在不同的框架中,实现不同,以Logback为例:

这个实现的类被设计为或者简单的返回一个默认的LoggerContext(LoggerContext是ILoggerFactory在logback中的实现),或者通过ContextSelector(logback特有的)来选择一个LoggerContext并返回。 

四、源码中的门面模式

3.1 Spring JDBC中的JdbcUtils对原生的JDBC进行封装,让调用者统一访问。

public abstract class JdbcUtils {

    //公共关闭链接
    public static void closeConnection(Connection con) {
        if (con != null) {
            try {
                con.close();
            }
            catch (SQLException ex) {
                logger.debug("Could not close JDBC Connection", ex);
            }
            catch (Throwable ex) {
                // 没有依赖jdbc驱动时,抛出异常
                logger.debug("Unexpected exception on closing JDBC Connection", ex);
            }
        }
    }

    //获取结果集的某一条数据
    public static Object getResultSetValue(ResultSet rs, int index) throws SQLException {
		Object obj = rs.getObject(index);
		String className = null;
		if (obj != null) {
			className = obj.getClass().getName();
		}
		if (obj instanceof Blob) {
			obj = rs.getBytes(index);
		}
		else if (obj instanceof Clob) {
			obj = rs.getString(index);
		}
		else if (className != null &&
				("oracle.sql.TIMESTAMP".equals(className) ||
				"oracle.sql.TIMESTAMPTZ".equals(className))) {
			obj = rs.getTimestamp(index);
		}
		else if (className != null && className.startsWith("oracle.sql.DATE")) {
			String metaDataClassName = rs.getMetaData().getColumnClassName(index);
			if ("java.sql.Timestamp".equals(metaDataClassName) ||
					"oracle.sql.TIMESTAMP".equals(metaDataClassName)) {
				obj = rs.getTimestamp(index);
			}
			else {
				obj = rs.getDate(index);
			}
		}
		else if (obj != null && obj instanceof java.sql.Date) {
			if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) {
				obj = rs.getTimestamp(index);
			}
		}
		return obj;
	}
   //省略...
}

3.2 Tomcat 中大量使用了门面模式。

Tomcat 中有很多不同组件,每个组件要相互交互数据,用门面模式隔离数据是个很好的方法。在Tomcat源码中搜索Facade(门面):

其中拿RequestFacade.class来说,它是HttpServletRequest外观类,里面封装了各种操作request的常见方法,比如getParameter方法等。

Request.class中封装了 HttpRequest 接口能够提供的数据,是子系统的门面。实际项目中对request进行操作的时候,其实使用的都是RequestFacade这个外观类对象:

protected RequestFacade facade = null;

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

门面模式是一个很好的封装方法,一个子系统比较复杂时,比如算法或者业务比较复杂,就可以封装出一个或多个门面出来,项目的结构简单,而且扩展性非常好。

门面模式提供了外界对子系统的访问黑箱操作,无论内部怎么变化,对外部访问者来说,还是同一个门面,同一个方法。

参考:

设计模式 | 外观模式及典型应用

© 著作权归作者所有

共有 人打赏支持
我叫刘半仙
粉丝 252
博文 28
码字总数 59709
作品 0
西安
程序员
私信 提问
设计模式15——Template Method设计模式

Template Method模板方法设计模式定义一个操作中算法的骨架,将具体步骤的执行延迟到子类中实现。Java中的抽象类就是使用了模板方法设计模式。模板方法设计模式结构如下: 以文档处理为例,T...

小米米儿小
2014/01/24
0
0
从 Java 到 Scala (三): object 的应用

本文由 Captain 发表在 ScalaCool 团队博客。 在上篇 Java 到 Scala 系列中,我想你或多或少在语言特性上对有了一定的掌握,在了解完它酷酷的语言特性——让静态回归常态并能简单运用其衍生出...

ScalaCool
2018/09/10
0
0
设计模式知识汇总(附github分享)

写在前面 主要内容 为了更系统的学习设计模式,特地开了这样一个基于Java的设计模式【集中营】,都是笔者在实际工作中用到过或者学习过的一些设计模式的一些提炼或者总检。慢慢地初见规模,也...

landy8530
2018/10/10
0
0
设计模式 2014-12-19

book: 阎宏《JAVA与模式》 架构设计栏目 http://blog.csdn.net/enterprise/column.html 概要: http://bbs.csdn.net/forums/Embeddeddriver 23种设计模式分别是: 1.单例模式 2.工厂方法模式...

jayronwang
2014/12/19
0
0
python 与设计模式 ——工厂与单例

python 与设计模式 源码地址:[http://git.oschina.net/duoduo3_69/python_design_pattern][1] git checkout v001(这个版本与此篇博客相符) zarkpy里面运用了很多设计模式,以前一直很费解p...

duoduo3_69
2013/11/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

开始看《JSP&Servlet学习笔记》

1:WEB应用简介。其中1.2.1对Web容器的工作流程写得不错 2:编写Servlet。搞清楚了Java的Web目录结构,以及Web.xml的一些配置作用。特别是讲了@WebServlet标签 3:请求与响应。更细致的讲了从...

max佩恩
41分钟前
1
0
mysql分区功能详细介绍,以及实例

一,什么是数据库分区 前段时间写过一篇关于mysql分表的的文章,下面来说一下什么是数据库分区,以mysql为例。mysql数据库中的数据是以文件的形势存在磁盘上的,默认放在/mysql/data下面(可...

吴伟祥
42分钟前
2
0
SQL语句查询

1.1 排序 通过order by语句,可以将查询出的结果进行排序。放置在select语句的最后。 格式: SELECT * FROM 表名 ORDER BY 排序字段ASC|DESC; ASC 升序 (默认) DESC 降序 1.查询所有商品信息,...

stars永恒
58分钟前
2
0
IntelliJ IDEA 第一个 Scala 程序

IntelliJ 安装完成 Scala 插件后,你需要尝试使用 IntelliJ 来创建并且运行第一个程序。 通常这个程序只是简单的输出 Hello World。 创建一个新工程 在文件下面选择新建,然后选择创建工程。...

honeymose
今天
2
0
csapp 习题 - 如何实现异或 exclusive-or

阅读 csapp v3 时,练习题 2.13 很有意思。练习题描述如下。 位设置是对于参数 mask 中每一个为 1 的位,那么参数 x 中相应位则被设置为 1 ;位清除是对于参数 mask 中每一个为 1 的位,那么...

ylme
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部