1. 程式人生 > >設計模式(6)—— 結構型 ——外觀(Facade)

設計模式(6)—— 結構型 ——外觀(Facade)

簡介

  • 定義:又叫門面模式,提供一個統一的介面,用來訪問子系統中的一群介面。
  • 解釋:外觀模式定義了一個高層介面,讓子系統更容易被使用。
  • 型別:結構型
  • 適用場景:
    • 子系統越來越複雜,外觀模式能夠提供簡單的呼叫介面。
    • 構建多層系統結構,利用外觀物件作為每層的入口,簡化層間呼叫。
  • 優點:
    • 簡化呼叫過程,無需瞭解深入子系統,防止帶來風險
    • 減少系統依賴,鬆散耦合
    • 更好地劃分訪問層次
    • 符合迪米特法則,即最少知道原則
  • 缺點:
    • 增加,擴充套件子系統的行為容易引入風險
    • 不符合開閉原則
  • 相關設計模式
    • 外觀模式和中介模式
    • 外觀模式和單例模式
    • 外觀模式和抽象工廠模式

程式碼實現

業務場景:在網上書店,我們假定購買一本書系統需要經過三個流程:

  • 身份校驗(CheckUser)。檢測使用者是否登陸,沒有登陸則強制要求使用者先登入
  • 支付校驗(Payment)。檢查金額數量,餘額等,並進行線上支付。
  • 物流系統(Logistics)。支付成功後,交由物流系統處理,並實時顯示物流資訊等操作。

下面是相關的業務流程示意圖:

外觀模式用例圖

從上面示意圖,很容易理解外觀模式中所謂的 “又稱為門面模式,它提供一個統一的介面,用來訪問子系統中的一群介面” 。各個service就是底層子系統的一群介面。這個”門面“既對外保持訪問子系統的統一介面,對內又能訪問子系統中的一群介面。

下面檢視具體程式碼實現:

首先是最簡單的兩個實體類User,Book,為了展現外觀設計模式,對具體的實現作了一定的簡化。

使用者類: 很簡單的實現,僅僅定義兩個簡單欄位isLogin,remainingMoney分別表示使用者登入狀態和賬戶餘額。

/**
 * 使用者類。
 * 這裡為演示方便,做簡單處理:定義兩個欄位。
 * isLogin:標識使用者是否登陸
 * remainingMoney:標識使用者的餘額
 */
public class User { // 使用者登入標誌。這裡預設使用者是登陸的 public static boolean isLogin = true; // 簡單初始化使用者的餘額為 46 元 private static double remainingMoney = 46; public static double getRemainingMoney() { return remainingMoney; } public static void consume(double price){ if( price > remainingMoney ){ throw new RuntimeException("消費金額不能大於餘額"); } remainingMoney -= price; } }

定義簡單的商品類,因為我們的業務場景是網上書店。定義一個Book類。同樣簡單起見我們只使用了兩本書作為例子演示。一本書大於使用者的預設餘額,另外一本書小於使用者的預設餘額。

/**
 * 定義一個商品類。
 * 簡單地定義兩個欄位:商品名稱,價格
 */
public class Book {

    private String name;
    private double price;

    /*
    getter 和 setter 方法
     */

    public Book(String name){

        this.name = name;

        //為簡單起見,如下方式定義書的價格

        if( "設計模式".equals(name) ) {
            this.price = 35;
        } else if( "演算法設計".equals(name) ) {
            this.price = 49;
        } else {
            //預設書的價格46
            this.price = 46;
        }

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

下面直接檢視測試類Test。從整體上檢視這個外觀的介面:

public class Test {
    public static void main(String[] args) {
        // 業務場景:現在我買了一本書,它叫《設計模式》(它35元)

        // 1. 買一本書:
        Book book = new Book("設計模式");
        //Book book1 = new Book("演算法設計");

        // 2. 提交系統。系統進行購物邏輯處理
        int resCode = ShoppingService.shopping(book);
        //int resCode2 = ShoppingService.shopping(book1);

        // 下面是對購物中系統內部出現的錯誤,進行相應的處理。
        // 這裡就不具體實現了

    }
}

可以看到,這跟現實生活中的例子一樣:我們只需要接觸我們所買的書——
new Book("設計模式"),然後直接提交給購物系統就行了——ShoppingService.shopping(book)

下面檢視外觀模式中的對客戶端的外觀。也就是ShoppingService類。

/**
 * 定義子系統(Shopping購物系統)的外觀模式類。
 * 外面通過這個類的介面就可以實現系統的功能了。
 *
 * 外界通過shopping的返回值能夠確定shopping流程的一些錯誤
 */
public class ShoppingService {

    public static final int LOGIN_FAiLURE = 0;     // 登陸失敗
    public static final int SUCCESS = 1;           // 購買成功
    public static final int PAYMENT_FAILURE = 2;   // 支付失敗
    public static final int LOGISTICS_FAITURE = 3;     // 物流處理故障

    // 驗證使用者身份介面服務
    private static CheckUserService checkUserService = new CheckUserService();
    // 支付介面服務
    private static PaymentService paymentService = new PaymentService();
    // 物流介面服務
    private static LogisticsService logisticsService = new LogisticsService();

    // 對外的唯一介面
    public static int shopping(Book shopItem){

        // 1. 檢查使用者身份(登陸狀態)
        if( checkUserService.isLogin() ){

            System.out.println("使用者是登陸的.");

            // 2. 支付
            if( paymentService.pay( shopItem ) ) {

                // 3. 物流
                if( logisticsService.handleLogistics() ){
                    System.out.println("購物成功");
                    //物流處理順利,這裡我們直接返回正確程式碼:ShoppingService.SUCCESS
                    return ShoppingService.SUCCESS;

                } else { // 物流有錯,返回錯誤程式碼

                    System.out.println("物流出錯啦.");
                    return ShoppingService.LOGISTICS_FAITURE;

                }

            } else {  // 支付失敗,返回錯誤程式碼

                System.out.println("支付失敗, 餘額不足.");
                return ShoppingService.PAYMENT_FAILURE;

            }
        } else {

            // 使用者沒登陸,返回錯誤程式碼
            System.out.println("使用者登入失敗.");
            return ShoppingService.LOGIN_FAiLURE;

        }
    }

}

暫時不討論各個service成員的具體實現。我們這個外觀類中儲存了子系統中的所有介面,把它們作為類的成員變數。 然後外部呼叫此外觀類的介面(這裡是shopping函式),ShoppingService 外觀類在處理外部請求任務的邏輯中,充分的利用了已經儲存的子系統的一系列介面,實現相關邏輯。

值得說明的是,shopping函式返回的程式碼:0,1,2,3與外觀模式關係不大,不過對於很好的理解此類業務邏輯有幫助。

下面是子系統內部的三個介面的實現邏輯:

CheckUserService類檢查使用者是否登陸。

public class CheckUserService {

    /**
     * 簡單驗證使用者身份:使用者是否登陸
     * @return true/false 登陸與否
     */
    public boolean isLogin(){
        return User.isLogin;
    }

}

PaymentService類實現支付邏輯

/**
 * 支付服務,PaymentService
 */
public class PaymentService {

    public boolean pay(Book shopItem){

        // 如果餘額還夠的話,就能夠進行支付
        if( shopItem.getPrice() <= User.getRemainingMoney() ){

            System.out.println("支付中...");
            User.consume( shopItem.getPrice() );
            System.out.println("支付成功...");

            // 支付成功,返回true;
            return true;
        }

        // 餘額不足,支付失敗,返回false
        return false;

    }

}

LogisticsService類實現物流相關的邏輯,這裡僅僅簡單的表示一下。

/**
 * 物流服務類。
 * 完成支付後,處理物流相關邏輯。
 */
public class LogisticsService {

    //處理物流,為演示方面直接返回true
    public boolean handleLogistics(){
        System.out.println("處理物流...");
        return true;
    }
}

UML圖

下面是這個程式碼工程專案的UML圖:
外觀模式例項程式碼UML圖

總結

值得注意的是,上面的程式碼分析是自頂向下的方式(自客戶端程式碼實現到底層程式碼實現,自Test類到外觀類再到子系統的介面群)。