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

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

 一、門面模式介紹

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

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

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

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

類型:結構型。

類圖:

1620uploading.4e448015.gif轉存失敗重新上傳取消

二、門面模式簡單實例

小明想開一個餐館,要去政府部門辦理衛生許可證、辦理稅務登記和辦理工商登記,以前小明要一一親自去辦理,這不是一件容易的事。

政府最近簡化了政務辦理流程,只用小明訪問一次簡化政務辦理的門面就可以辦理到全部證件。

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();
    }

}

三、日誌門面

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

1620uploading.4e448015.gif轉存失敗重新上傳取消

其中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方法:

1620uploading.4e448015.gif轉存失敗重新上傳取消

上圖的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爲例:

1620uploading.4e448015.gif轉存失敗重新上傳取消

這個實現的類被設計爲或者簡單的返回一個默認的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(門面):

1620uploading.4e448015.gif轉存失敗重新上傳取消

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

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

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

參考:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章