從原始碼角度理解Java設計模式——門面模式
一、門面模式介紹
門面模式定義:也叫外觀模式,定義了一個訪問子系統的介面,除了這個介面以外,不允許其他訪問子系統的行為發生。
適用場景:子系統很複雜時,增加一個介面供外部訪問。
優點:簡化層級間的呼叫,減少依賴,防止風險。
缺點:如果設計不當,增加新的子系統可能需要修改門面類的原始碼,違背了開閉原則。
型別:結構型。
類圖:
二、日誌門面
阿里巴巴開發手冊中有這樣一條規定:
其中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為例:
三、原始碼中的門面模式
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;
}
門面模式是一個很好的封裝方法,一個子系統比較複雜時,比如演算法或者業務比較複雜,就可以封裝出一個或多個門面出來,專案的結構簡單,而且擴充套件性非常好。
門面模式提供了外界對子系統的訪問黑箱操作,無論內部怎麼變化,對外部訪問者來說,還是同一個門面,同一個方法。
參考: