2019独角兽企业重金招聘Python工程师标准>>>
一、门面模式介绍
门面模式定义:也叫外观模式,定义了一个访问子系统的接口,除了这个接口以外,不允许其他访问子系统的行为发生。
适用场景:子系统很复杂时,增加一个接口供外部访问。
优点:简化层级间的调用,减少依赖,防止风险。
缺点:如果设计不当,增加新的子系统可能需要修改门面类的源代码,违背了开闭原则。
类型:结构型。
类图:
二、门面模式简单实例
小明想开一个餐馆,要去政府部门办理卫生许可证、办理税务登记和办理工商登记,以前小明要一一亲自去办理,这不是一件容易的事。
政府最近简化了政务办理流程,只用小明访问一次简化政务办理的门面就可以办理到全部证件。
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;
}
门面模式是一个很好的封装方法,一个子系统比较复杂时,比如算法或者业务比较复杂,就可以封装出一个或多个门面出来,项目的结构简单,而且扩展性非常好。
门面模式提供了外界对子系统的访问黑箱操作,无论内部怎么变化,对外部访问者来说,还是同一个门面,同一个方法。
参考:
设计模式 | 外观模式及典型应用