從源碼角度理解Java設計模式——門面模式
一、門面模式介紹
門面模式定義:也叫外觀模式,定義了一個訪問子系統的接口,除了這個接口以外,不允許其他訪問子系統的行爲發生。
適用場景:子系統很複雜時,增加一個接口供外部訪問。
優點:簡化層級間的調用,減少依賴,防止風險。
缺點:如果設計不當,增加新的子系統可能需要修改門面類的源代碼,違背了開閉原則。
類型:結構型。
類圖:
二、門面模式簡單實例
小明想開一個餐館,要去政府部門辦理衛生許可證、辦理稅務登記和辦理工商登記,以前小明要一一親自去辦理,這不是一件容易的事。
政府最近簡化了政務辦理流程,只用小明訪問一次簡化政務辦理的門面就可以辦理到全部證件。
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; }
門面模式是一個很好的封裝方法,一個子系統比較複雜時,比如算法或者業務比較複雜,就可以封裝出一個或多個門面出來,項目的結構簡單,而且擴展性非常好。
門面模式提供了外界對子系統的訪問黑箱操作,無論內部怎麼變化,對外部訪問者來說,還是同一個門面,同一個方法。
參考: