淺談 Facade 模式
Facade 模式
所謂 Facade 模式,是一個可以讓事情變得有點頭緒的好東西。
一個 Facade 肯定是一位某方面的“行家”,例如資料庫操作。它對來自上層的請求遮蔽了具體的業務邏輯細節,任何程式需要對資料庫進行 CRUD 操作時,只需要告訴 Facade 層“我要做什麼”,而 Facade 層則知道“到哪裡去做”,於是它根據請求的具體內容,呼叫相應的底層模組(在那裡解決“到底怎樣做”的問題)。進行具體的操作。當它從底層模組中取得所需的資料後,再將結果返回給上層程式。
Facade 具體應用
一個 Facade 模組可以針對一種需求,處理來自各方面的請求。例如在一個使用者註冊模組中,當前的需求描述為“只要使用者名稱沒有衝突,就允許註冊”。因為這是一種很寬鬆的註冊策略,所以我們不妨把相應的 Facade 類稱為 LenientFacade。
這個 Facade 類首先根據使用者輸入的資訊構造出一個 UserBean,然後檢測該使用者名稱是否已被佔用。如果未被佔用,就呼叫 DAO 執行增加記錄操作,否則就丟擲一個“使用者名稱已存在”的異常。
public class LenientFacade {
public int insertUser (ActionForm actionForm) throws ... {
int row;
UserForm userForm = (UserForm) actionForm;
// 根據formBean生成UserBean
UserBean userBean = new UserBean();
BeanUtils.copyProperties(userBean, userForm);
// 執行資料庫插入操作,首先檢視該使用者是否存在。
setUserDAO(new UserDAOibatis());
UserBean user = dao.loadUserByUsername(userBean.getUsername());
if (user != null) {
// 同名使用者已存在,丟擲一個自定義的異常 UserAlreadyExistException。
throw new UserAlreadyExistException("User already exist.");
}
row = dao.insertUser(userBean);
return row; // 返回該使用者的id(AUTO_INCREMENT)。
}
public void setUserDAO (UserDAO dao) {
this.dao = dao;
}
private UserDAO dao;
}
在某一時期,可能因為各種政策的原因,網站需要對註冊使用者進行更為嚴格的稽核。所以我們需要 Facade 具有不同的業務邏輯。不妨把負責這種更嚴格的註冊過程的 Facade 類稱為 StrictFacade。
StrictFacade 除了驗證使用者名稱是否可用外,還要驗證使用者的國籍、年齡、學歷等等一大堆事情。下面是虛擬碼:
public class StrictFacade {
public int insertUser (ActionForm actionForm) throws ... {
... //和前面相同的程式碼
// 執行資料庫插入操作,首先檢視該使用者是否存在。
setUserDAO(new UserDAOibatis());
UserBean user = dao.loadUserByUsername(userBean.getUsername());
if (user != null) {
// 同名使用者已存在,丟擲一個自定義的異常 UserAlreadyExistException。
throw new UserAlreadyExistException("User already exist.");
}
try {
// 許多許多更嚴格的審查
// 許多許多更嚴格的審查
}
catch (... ) {
...
throw 五花八門的異常
}
row = dao.insertUser(userBean);
return row; // 返回該使用者的id(AUTO_INCREMENT)。
}
public void setUserDAO (UserDAO dao) {
this.dao = dao;
}
private UserDAO dao;
}
執行更加嚴格的註冊過程,並不意味著原有的“寬鬆式”註冊就沒有用了。網站可能需要隨時根據情況在兩種策略之間切換。顯然,每次切換註冊過程就停掉伺服器,重新編譯 Facade 層的程式碼,然後再重啟伺服器,會讓老闆和所有的使用者瘋掉!
這時,使用靈活的抽象工廠模式就可以解決這個問題。
.
結合抽象工廠模式的 Facade 模式
抽象工廠模式可以根據初始引數的不同,生產出適應各種需求的具體實現類。我們可以設計兩種工廠,一種是 LenientFacadeFactory,另一種是 StrictFacadeFactory,二者分別負責生產“寬鬆式”和“嚴格式”的 Facade。
這兩個工廠都繼承自他們的父類 —— 一個抽象的 FacadeFactory。這個抽象的 Factory 使用一個靜態方法返回具體的 Factory 類,並且為它的子類們定義了獲得Facade的get方法:public abstract Facade getFacade();,所有繼承了抽象工廠的具體工廠類,都要負責為其呼叫者返回一個實際可用的 Facade。
抽象的 Facade 工廠程式碼如下:
public abstract class FacadeFactory {
public static FacadeFactory getInstance(int facadeFactoryType) {
switch (facadeFactoryType) {
case LENIENT:
return new LenientFacadeFactory();
case STRICT:
return new StrictFacadeFactory();
default:
return null;
}
}
public abstract Facade getFacade();
public static final int LENIENT = 0;
public static final int STRICT = 1;
}
下面是 LenientFacadeFactory 的程式碼,Strict 版本的與其類似。
public class LenientFacadeFactory extends FacadeFactory {
public Facade getFacade() {
return new LenientFacade();
}
}
通過這段程式碼,我們就可以得到本文一開始的那段程式碼中的 LenientFacade 了。
在程式(在Strut中應該是一個Action)中的具體呼叫方法是:
int facadeType = FacadeFactory.LENIENT;
FacadeFactory factory = FacadeFactory.getInstance(facadeType);
Facade facade = factory.getFacade();
try {
row = facade.insertUser(userForm);
}
catch (...) {
...
}
這樣做的好處很明顯:Servlet (或具體為一個Action)不需要去產生出一個具體的 Facade,所有的方法呼叫都是建立在統一的介面之上。當我們需要一個不同型別的 Facade 的時候,只需要調整上面程式碼中第一行的 facadeType 變數,就能產生出相應的 Facade。因此實現了 Controller 層和 Model 層的鬆耦合。
.
通過配置初始化引數實現“更鬆的”耦合
首先宣告!這並不是一個很好解決方案,這僅僅是為了複習鞏固所學的知識而自創的辦法。
上面的程式碼中,問題沒有得到根本的解決,因為那個代表著 facade 型別的 facadeType 變數仍舊被硬編碼在程式中了。不過這個問題並不難解決,只要考慮到 ActionServlet 的本質是一個 Servlet,我們就可以通過為該 Servlet 設定一個初始化引數解決它。
可以在 web.xml 配置描述符中,為 ActionServlet 增加一個初始化引數:
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>facadeType</param-name>
<param-value>LENIENT</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
然後,我們在程式中就可以讀取這個初始化引數,進而得到適用的 FacadeFactory 了。
String facadeType = this.getServlet().getInitParameter("facadeType");