1. 程式人生 > >從原始碼角度理解Java設計模式——門面模式

從原始碼角度理解Java設計模式——門面模式

一、門面模式介紹

門面模式定義:也叫外觀模式,定義了一個訪問子系統的介面,除了這個介面以外,不允許其他訪問子系統的行為發生。

適用場景:子系統很複雜時,增加一個介面供外部訪問。

優點:簡化層級間的呼叫,減少依賴,防止風險。

缺點:如果設計不當,增加新的子系統可能需要修改門面類的原始碼,違背了開閉原則。

型別:結構型。

類圖:

 

二、日誌門面

阿里巴巴開發手冊中有這樣一條規定:

-w922

其中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;
}

門面模式是一個很好的封裝方法,一個子系統比較複雜時,比如演算法或者業務比較複雜,就可以封裝出一個或多個門面出來,專案的結構簡單,而且擴充套件性非常好。

門面模式提供了外界對子系統的訪問黑箱操作,無論內部怎麼變化,對外部訪問者來說,還是同一個門面,同一個方法。

參考:

設計模式 | 外觀模式及典型應用