Objective-C設計模式解析-外觀

weixin_34127717發表於2017-01-02

看圖識模式

自助遊(圖1)

clipboard.png

旅行社(圖2)

clipboard.png

舉個例子

不知道大家有沒有通過旅行社報團出去旅遊的經歷?這是一個很好的外觀模式的應用。

方式一:客戶直接呼叫各個子系統的功能,和各個子系統之間形成緊耦合的關係(上圖一)
方式二:提供一個高層介面,該高層介面負責和子系統進行互動,並向客戶提供需要使用的介面(上圖二)
從上面兩種方式的圖式結構可以看到,對客戶來說,方式二比方式一要好用很多,因為在方式二中,客戶不需要知道各個子系統的邏輯,只需要和高層介面互動就可以了。實際上方式二,就是我們這裡要說的外觀模式了。

如果這裡不應用外觀模式,我們(上圖中的Client),就得自己去聯絡交通工具、預定旅館、飯館、景點門票等,相信這樣的旅程,大家會感覺很累。有了外觀角色(上圖中的Facade),它會幫我們去處理這些事情。

迪米特法則

簡介

得墨忒耳定律(Law of Demeter,縮寫LoD)亦稱為“最少知識原則(Principle of Least Knowledge)”,是一種軟體開發的設計指導原則,特別是物件導向的程式設計。得墨忒耳定律是鬆耦合的一種具體案例。該原則是美國東北大學在1987年末在發明的。
這個原理的名稱來源於希臘神話中的農業女神,孤獨的得墨忒耳(Demeter)。

定義:一個物件應該對其他物件保持最少的瞭解。
問題由來:類與類之間的關係越密切,耦合度越大,當一個類發生改變時,對另一個類的影響也越大。

解決方案:儘量降低類與類之間的耦合。

自從我們接觸程式設計開始,就知道了軟體程式設計的總的原則:低耦合,高內聚。無論是程式導向程式設計還是物件導向程式設計,只有使各個模組之間的耦合儘量的低,才能提高程式碼的複用率。低耦合的優點不言而喻,但是怎麼樣程式設計才能做到低耦合呢?那正是迪米特法則要去完成的。
迪米特法則又叫最少知道原則,最早是在1987年由美國Northeastern University的Ian Holland提出。通俗的來講,就是一個類對自己依賴的類知道的越少越好。也就是說,對於被依賴的類來說,無論邏輯多麼複雜,都儘量地的將邏輯封裝在類的內部,對外除了提供的public方法,不對外洩漏任何資訊。迪米特法則還有一個更簡單的定義:只與直接的朋友通訊。首先來解釋一下什麼是直接的朋友:每個物件都會與其他物件有耦合關係,只要兩個物件之間有耦合關係,我們就說這兩個物件之間是朋友關係。耦合的方式很多,依賴、關聯、組合、聚合等。其中,我們稱出現成員變數、方法引數、方法返回值中的類為直接的朋友,而出現在區域性變數中的類則不是直接的朋友。也就是說,陌生的類最好不要作為區域性變數的形式出現在類的內部。

一些比喻

最常見的比喻是:不要和陌生人說話

看看這個:假設我在便利店購物。付款時,我是應該將錢包交給收銀員,讓她開啟並取出錢?還是我直接將錢遞給她?

再做一個比喻:人可以命令一條狗行走(walk),但是不應該直接指揮狗的腿行走,應該由狗去指揮控制它的腿如何行走。

模式與意義

迪米特法則可以簡單說成:talk only to your immediate friends。 對於OOD來說,又被解釋為下面幾種方式:一個軟體實體應當儘可能少的與其他實體發生相互作用。每一個軟體單位對其他的單位都只有最少的知識,而且侷限於那些與本單位密切相關的軟體單位。
迪米特法則的初衷在於降低類之間的耦合。由於每個類儘量減少對其他類的依賴,因此,很容易使得系統的功能模組功能獨立,相互之間不存在(或很少有)依賴關係。
迪米特法則不希望類之間建立直接的聯絡。如果真的有需要建立聯絡,也希望能通過它的友元類來轉達。因此,應用迪米特法則有可能造成的一個後果就是:系統中存在大量的中介類,這些類之所以存在完全是為了傳遞類之間的相互呼叫關係——這在一定程度上增加了系統的複雜度。
設計模式中的門面模式(Facade)和中介模式(Mediator),都是迪米特法則應用的例子。
值得一提的是,雖然Ian Holland對電腦科學的貢獻也僅限於這一條法則,其他方面的建樹不多,但是,這一法則卻不僅僅侷限於計算機領域,在其他領域也同樣適用。比如,美國人就在航天系統的設計中採用這一法則。

模式定義

外觀模式為子系統中一組不同的介面提供統一的介面。外觀定義了上層介面,通過降低複雜度和隱藏子系統間的通訊及依存關係,讓子系統易於使用。

API 的使用者完全不知道這內部的業務邏輯有多麼複雜。當我們有大量的類並且它們使用起來很複雜而且也很難理解的時候,外觀模式是一個十分理想的選擇。
外觀模式把使用和背後的實現邏輯成功解耦,同時也降低了外部程式碼對內部工作的依賴程度。如果底層的類發生了改變,外觀的介面並不需要做修改。

結構圖

clipboard.png

這個定義,通過上面引言的圖示講解,應該很好理解了,這裡再分析一下定義中的兩個重要角色:

外觀角色:就是引言圖示中的“高層介面”,客戶端可以呼叫這個角色的方法;另外,該角色知道相關的子系統的功能和責任。

子系統角色:可以同時有一個或者多個子系統。每一個子系統都可以被客戶端直接呼叫,或者被外觀角色呼叫。

程式碼

比方說今天我不想開車,於是打電話叫了計程車。只要計程車能把我送到目的地,我不在乎車牌和型號。我會直接對司機說“我要去somePlace”,然後司機會執行一系列的命令(鬆手剎、換擋、踩油門等等)。計程車司機抽象出了駕駛汽車的底層負責操作的細節。他通過提供駕駛服務(簡化了介面),把我與原本複雜的車輛操作介面分離。計程車和我直接的介面只是一個簡單的“我要去xxx”命名。

很多舊的物件導向應用程式中,可能有許多類分散於帶有各種功能的系統中。要把這些類用於某個功能,需要知道全部細節才能在一組演算法中使用它們。如果從邏輯上將其中一些類組合成一個簡單的介面,可以讓這些類更易於使用。

clipboard.png

編寫程式碼

Car定義了幾個操其內部物件的方法,如pressBrakes、releaseBrakes、changGears...。客戶端想要使用Car的內部功能,必須瞭解如何使用這些方法進行正確操作。

@interface Car : NSObject

/// 踩剎車
- (void)pressBrakes;
/// 鬆剎車
- (void)releaseBrakes;
/// 換擋
- (void)changGears;
/// 踩油門
- (void)pressAccelerator;
/// 鬆油門
- (void)releaseAccelerator;

@end 


@implementation Car

- (void)pressBrakes
{
    NSLog(@"car: 踩剎車");
}

- (void)releaseBrakes
{
    NSLog(@"car: 鬆剎車");
}

- (void)changGears
{
    NSLog(@"car: 換擋");
}

- (void)pressAccelerator
{
    NSLog(@"car: 踩油門");
}

- (void)releaseAccelerator
{
    NSLog(@"car: 鬆油門");
}

@end

Taximeter本身是一個複雜的系統,它有兩個讓客戶端操作其物件的方法。start和stop方法只是讓Taximeter開始或停止。這裡不深入Taximeter的細節。

@interface Taximeter : NSObject

/// 開始打表
- (void)start;
/// 結束打表
- (void)stop;

@end  


@implementation Taximeter

- (void)start
{
    NSLog(@"Taximeter: 開始打表");
}

- (void)stop
{
    NSLog(@"Taximeter: 結束打表");
}

@end  

目前,計程車服務系統裡面有兩個複雜的子系統。需要一個CarDriver作為外觀以簡化介面。程式碼如下

@interface CarDriver : NSObject

- (void)driveToLocation:(CGPoint)place;

@end  


@implementation CarDriver

- (void)driveToLocation:(CGPoint)place
{
    // ....
    
    // 開啟計價器
    Taximeter *meter = [Taximeter new];
    [meter start];
    
    // 操作車輛,直到到達位置place
    Car *car = [Car new];
    [car releaseBrakes];
    [car changGears];
    [car pressAccelerator];
    
    // ....
    
    // 到達位置place,停下車和計價器
    [car releaseAccelerator];
    [car pressBrakes];
    [meter stop];
    
    // ....
}

@end  

CarDriver的外觀方法決定了客戶端可以用多簡單的方式使用整個計程車服務系統。客戶只需呼叫driveToLocation:方法,其餘的操作就會在訊息呼叫中發生。客戶端不需要了解底層發生的一切。

總結

通過上面的講解,我們來分析一下外觀模式的特點:

  • Facade設計模式注重從架構的層次去看整個系統,而不是單個類的層次。很多時候,它是一種架構設計模式,比如我們常用的三層架構。

  • Facade模式簡化了整個系統的介面,同時對於系統內部(子系統)與外部程式(Client)來說,也達到了一種“解耦”的效果。

根據外觀模式的特點,我們可以在以下情況中使用Facade模式:

  • 不需要使用一個複雜系統的所有功能,只需要使用系統的部分功能時,那麼應用Façade模式提供一個高層介面將比直接呼叫原系統的介面簡單得多。

  • 希望封裝或者隱藏原系統的介面時,可以考慮使用外觀模式。

  • 希望使用原系統的部分功能,而且還希望增加一些新的功能。

  • 構建一個具有層次結構的子系統時,使用Facade模式定義子系統中每層的高階介面。如果子系統之間是相互依賴的,你可以讓它們僅通過Facade進行通訊,從而簡化了它們之間的依賴關係。

其實這個設計模式我們很常見,一般我們使用第三方類的時候都會有這種模式,使用第三方時我們只需要引用第三方的其中改一個檔案就能滿足很多功能的使用。我只這個檔案就是講子系統的一些方法歸併到了這個檔案中,從而使使用者上手更快。

相關文章