1. 程式人生 > >淺談 Facade 模式

淺談 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");