一、定义
门面模式(Facade Pattern)又叫外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。其主要特征是定义了一个高层接口,让子系统更容易使用,属于结构性模式。其实,在我们日常的编码工作中,经常使用门面模式,但凡只要高层模块需要调度多个子系统(2个以上类对象),我们都会自觉地创建一个新类封装这些子系统,提供精简接口,让高层模块可以更加容易间接调用这些子系统的功能。尤其是现阶段各种第三方SDK,各种开源类库,很大概率都会使用门面模式。
门面模式主要包含2种角色:
外观角色(Facade):也称门面角色,系统对外的统一接口;
子系统角色(SubSystem):可以同时有一个或多个 SubSystem。每个 SubSytem 都不是一个单独的类,而是一个类的集合。 SubSystem 并不知道 Facade 的存在,对于 SubSystem 而言, Facade 只是另一个客户端而已(即 Facade 对 SubSystem 透明)。
二、门面模式的应用场景
SSM架构就是一个很好的门面模式说明,多个业务的调用封装成一个接口给用户调用;
子系统角色中的类:public class ModuleA { //示意方法 public void testA(){ System.out.println("调用ModuleA中的testA方法"); } }
public class ModuleB { //示意方法 public void testB(){ System.out.println("调用ModuleB中的testB方法"); } }
public class ModuleC { //示意方法 public void testC(){ System.out.println("调用ModuleC中的testC方法"); } }
门面角色类:
public class Facade { //示意方法,满足客户端需要的功能 public void test(){ ModuleA a = new ModuleA(); a.testA(); ModuleB b = new ModuleB(); b.testB(); ModuleC c = new ModuleC(); c.testC(); } }
客户端角色类:
public class Client { public static void main(String[] args) { Facade facade = new Facade(); facade.test(); } }
Facade类其实相当于A、B、C模块的外观界面,有了这个Facade类,那么客户端就不需要亲自调用子系统中的A、B、C模块了,也不需要知道系统内部的实现细节,甚至都不需要知道A、B、C模块的存在,客户端只需要跟Facade类交互就好了,从而更好地实现了客户端和子系统中A、B、C模块的解耦,让客户端更容易地使用系统。
三、源码中的应用
Spring JDBC模块下的JdbcUtils类,它封装了和JDBC相关的所有操作,像里面的什么closeConnection方法呀,他里面的逻辑调用其实就是个门面模式,他自己啥都没做,事都让
Connection 类做了,这里面还有很多方法的调用都这样
public abstract class JdbcUtils { public static final int TYPE_UNKNOWN = -2147483648; private static final Log logger = LogFactory.getLog(JdbcUtils.class); public JdbcUtils() { } public static void closeConnection(@Nullable Connection con) { if (con != null) { try { con.close(); } catch (SQLException var2) { logger.debug("Could not close JDBC Connection", var2); } catch (Throwable var3) { logger.debug("Unexpected exception on closing JDBC Connection", var3); } } } public static void closeStatement(@Nullable Statement stmt) { if (stmt != null) { try { stmt.close(); } catch (SQLException var2) { logger.trace("Could not close JDBC Statement", var2); } catch (Throwable var3) { logger.trace("Unexpected exception on closing JDBC Statement", var3); } } } public static void closeResultSet(@Nullable ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException var2) { logger.trace("Could not close JDBC ResultSet", var2); } catch (Throwable var3) { logger.trace("Unexpected exception on closing JDBC ResultSet", var3); } } } @Nullable public static Object getResultSetValue(ResultSet rs, int index, @Nullable Class<?> requiredType) throws SQLException { if (requiredType == null) { return getResultSetValue(rs, index); } else if (String.class == requiredType) { return rs.getString(index); } else { Object value; if (Boolean.TYPE != requiredType && Boolean.class != requiredType) { if (Byte.TYPE != requiredType && Byte.class != requiredType) { if (Short.TYPE != requiredType && Short.class != requiredType) { if (Integer.TYPE != requiredType && Integer.class != requiredType) { if (Long.TYPE != requiredType && Long.class != requiredType) { if (Float.TYPE != requiredType && Float.class != requiredType) { if (Double.TYPE != requiredType && Double.class != requiredType && Number.class != requiredType) { if (BigDecimal.class == requiredType) { return rs.getBigDecimal(index); } if (Date.class == requiredType) { return rs.getDate(index); } if (Time.class == requiredType) { return rs.getTime(index); } if (Timestamp.class != requiredType && java.util.Date.class != requiredType) { if (byte[].class == requiredType) { return rs.getBytes(index); } if (Blob.class == requiredType) { return rs.getBlob(index); } if (Clob.class == requiredType) { return rs.getClob(index); } if (requiredType.isEnum()) { Object obj = rs.getObject(index); if (obj instanceof String) { return obj; } if (obj instanceof Number) { return NumberUtils.convertNumberToTargetClass((Number)obj, Integer.class); } return rs.getString(index); } try { return rs.getObject(index, requiredType); } catch (AbstractMethodError var5) { logger.debug("JDBC driver does not implement JDBC 4.1 'getObject(int, Class)' method", var5); } catch (SQLFeatureNotSupportedException var6) { logger.debug("JDBC driver does not support JDBC 4.1 'getObject(int, Class)' method", var6); } catch (SQLException var7) { logger.debug("JDBC driver has limited support for JDBC 4.1 'getObject(int, Class)' method", var7); } String typeName = requiredType.getSimpleName(); if ("LocalDate".equals(typeName)) { return rs.getDate(index); } if ("LocalTime".equals(typeName)) { return rs.getTime(index); } if ("LocalDateTime".equals(typeName)) { return rs.getTimestamp(index); } return getResultSetValue(rs, index); } return rs.getTimestamp(index); } value = rs.getDouble(index); } else { value = rs.getFloat(index); } } else { value = rs.getLong(index); } } else { value = rs.getInt(index); } } else { value = rs.getShort(index); } } else { value = rs.getByte(index); } } else { value = rs.getBoolean(index); } return rs.wasNull() ? null : value; } } @Nullable 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) { Blob blob = (Blob)obj; obj = blob.getBytes(1L, (int)blob.length()); } else if (obj instanceof Clob) { Clob clob = (Clob)obj; obj = clob.getSubString(1L, (int)clob.length()); } else if (!"oracle.sql.TIMESTAMP".equals(className) && !"oracle.sql.TIMESTAMPTZ".equals(className)) { 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.getDate(index); } else { obj = rs.getTimestamp(index); } } else if (obj instanceof Date && "java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) { obj = rs.getTimestamp(index); } } else { obj = rs.getTimestamp(index); } return obj; } public static Object extractDatabaseMetaData(DataSource dataSource, DatabaseMetaDataCallback action) throws MetaDataAccessException { Connection con = null; Object var4; try { con = DataSourceUtils.getConnection(dataSource); DatabaseMetaData metaData = con.getMetaData(); if (metaData == null) { throw new MetaDataAccessException("DatabaseMetaData returned by Connection [" + con + "] was null"); } var4 = action.processMetaData(metaData); } catch (CannotGetJdbcConnectionException var10) { throw new MetaDataAccessException("Could not get Connection for extracting meta data", var10); } catch (SQLException var11) { throw new MetaDataAccessException("Error while extracting DatabaseMetaData", var11); } catch (AbstractMethodError var12) { throw new MetaDataAccessException("JDBC DatabaseMetaData method not implemented by JDBC driver - upgrade your driver", var12); } finally { DataSourceUtils.releaseConnection(con, dataSource); } return var4; } public static <T> T extractDatabaseMetaData(DataSource dataSource, String metaDataMethodName) throws MetaDataAccessException { return extractDatabaseMetaData(dataSource, (dbmd) -> { try { Method method = DatabaseMetaData.class.getMethod(metaDataMethodName, (Class[])null); return method.invoke(dbmd, (Object[])null); } catch (NoSuchMethodException var3) { throw new MetaDataAccessException("No method named '" + metaDataMethodName + "' found on DatabaseMetaData instance [" + dbmd + "]", var3); } catch (IllegalAccessException var4) { throw new MetaDataAccessException("Could not access DatabaseMetaData method '" + metaDataMethodName + "'", var4); } catch (InvocationTargetException var5) { if (var5.getTargetException() instanceof SQLException) { throw (SQLException)var5.getTargetException(); } else { throw new MetaDataAccessException("Invocation of DatabaseMetaData method '" + metaDataMethodName + "' failed", var5); } } }); } public static boolean supportsBatchUpdates(Connection con) { try { DatabaseMetaData dbmd = con.getMetaData(); if (dbmd != null) { if (dbmd.supportsBatchUpdates()) { logger.debug("JDBC driver supports batch updates"); return true; } logger.debug("JDBC driver does not support batch updates"); } } catch (SQLException var2) { logger.debug("JDBC driver 'supportsBatchUpdates' method threw exception", var2); } return false; } @Nullable public static String commonDatabaseName(@Nullable String source) { String name = source; if (source != null && source.startsWith("DB2")) { name = "DB2"; } else if ("Sybase SQL Server".equals(source) || "Adaptive Server Enterprise".equals(source) || "ASE".equals(source) || "sql server".equalsIgnoreCase(source)) { name = "Sybase"; } return name; } public static boolean isNumeric(int sqlType) { return -7 == sqlType || -5 == sqlType || 3 == sqlType || 8 == sqlType || 6 == sqlType || 4 == sqlType || 2 == sqlType || 7 == sqlType || 5 == sqlType || -6 == sqlType; } public static String lookupColumnName(ResultSetMetaData resultSetMetaData, int columnIndex) throws SQLException { String name = resultSetMetaData.getColumnLabel(columnIndex); if (name == null || name.length() < 1) { name = resultSetMetaData.getColumnName(columnIndex); } return name; } public static String convertUnderscoreNameToPropertyName(@Nullable String name) { StringBuilder result = new StringBuilder(); boolean nextIsUpper = false; if (name != null && name.length() > 0) { if (name.length() > 1 && name.substring(1, 2).equals("_")) { result.append(name.substring(0, 1).toUpperCase()); } else { result.append(name.substring(0, 1).toLowerCase()); } for(int i = 1; i < name.length(); ++i) { String s = name.substring(i, i + 1); if (s.equals("_")) { nextIsUpper = true; } else if (nextIsUpper) { result.append(s.toUpperCase()); nextIsUpper = false; } else { result.append(s.toLowerCase()); } } } return result.toString(); } }
四、总结
优点:
1、简化了调用过程,无需深入了解子系统,以防给子系统带来风险。
2、减少系统依赖、松散耦合
3、更好地划分访问层次,提高了安全性
4、遵循迪米特法则,即最少知道原则。
缺点:
1、当增加子系统和扩展子系统行为时,可能容易带来未知风险
2、不符合开闭原则
3、某些情况下可能违背单一职责原则。