設計模式(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圖:
總結
值得注意的是,上面的程式碼分析是自頂向下的方式(自客戶端程式碼實現到底層程式碼實現,自Test類到外觀類再到子系統的介面群)。