IOS设计模式之二(门面模式,装饰器模式)
生活随笔
收集整理的這篇文章主要介紹了
IOS设计模式之二(门面模式,装饰器模式)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
本文原文請見:http://www.raywenderlich.com/46988/ios-design-patterns.
由 @krq_tiger(http://weibo.com/xmuzyq)翻譯,如果你發現有什么錯誤,請與我聯系謝謝。 門面(Facade)模式(譯者注:facade有些書籍譯為門面,有些書籍譯為外觀,此處譯為門面)
?
?#import?"Album.h"?? ? 接下來,在LibraryAPI.h中增加如下的方法定義: -?(NSArray*)getAlbums;?? -?(void)addAlbum:(Album*)album?atIndex:(int)index;?? -?(void)deleteAlbumAtIndex:(int)index;?? #import?"PersistencyManager.h"?? #import?"HTTPClient.h"?? @interfaceLibraryAPI?()?{?? ????PersistencyManager?*persistencyManager;?? ????HTTPClient?*httpClient;?? ????BOOL?isOnline;?? ?? }?? @end?? -?(id)init{?? ????self?=?[super?init];?? ?? ????if?(self)?{?? ?? ????????persistencyManager?=?[[PersistencyManager?alloc]?init];?? ?? ????????httpClient?=?[[HTTPClient?alloc]?init];?? ?? ????????isOnline?=?NO;?? ?? ????}?? ?? ????return?self;?? ?? }?? ?-(NSArray*)getAlbums?? {?? ????return?[persistencyManager?getAlbums];?? }?? ?? ??? ?? -?(void)addAlbum:(Album*)album?atIndex:(int)index?? {?? ????[persistencyManager?addAlbum:album?atIndex:index];?? ?? ????if?(isOnline)?? ?? ????{?? ?? ????????[httpClient?postRequest:@"/api/addAlbum"?body:[album?description]];?? ?? ????}?? ?? }?? ?? ??? ?? -?(void)deleteAlbumAtIndex:(int)index?? {?? ?? ????[persistencyManager?deleteAlbumAtIndex:index];?? ?? ????if?(isOnline)?? ????{?? ?? ????????[httpClient?postRequest:@"/api/deleteAlbum"?body:[@(index)?description]];?? ?? ????}?? ?? }?? ?
?
Category(類別)是一種不需要子類化就可以讓你能動態的給已經存在的類增加方法的強有力的機制。新增的方法是在編譯期增加的,這些方法執行的時候和被擴展的類的其它方法是一樣的。它可能與裝飾器設計模式的定義稍微有點不同,因為Category(類別)不會保存被擴展類的引用。 注意:你除了可以擴展你自己的類以外,你還可以給Cocoa自己的類增加方法。?
如何使用類別 設想一種情況,你需要讓Album(專輯)對象顯示在一個表格視圖(TableView)中:
? 專輯的標題從何而來?因為專輯是模型對象,它本身不需要關心你如何顯示它的數據。你需要增加一些代碼去擴展專輯類的行為,但是不需要直接修改專輯類。 你將創建一個專輯類擴展的類別,它將定義一個新的方法,這個方法會返回能很容易和UITableViews使用的數據結構。這個數據結構如下圖所示:
? 為了給Album增加一個類別,導航到“File\New\File...\",選擇Objective-C category模板,不要習慣性的選擇Objective-C class模板。在Category域輸入TableRepresentation,Category on域輸入Album. 注意:你已經注意到了新建文件的名字了嗎?Album+TableRepresentation意味著你正在擴展Album類。這種約定是非常重要,因為它方便閱讀以及阻止和你或者其他人創建的類別沖突。 打開Album+TableRepresentation.h類,新增如下的方法原型: Objective-c代碼??-?(NSDictionary*)tr_tableRepresentation;?? ? 注意在方法開頭有一個tr_前綴,它是類別TableRepresentation的縮寫。再一次,這種約定也可以阻止和其它的方法沖突。 注意:如果方法與原來類的方法重名了,或者與同樣的類(甚至它的父類)的其它的擴展重名,那么運行期到底應該調用哪個方法是未定義的。當你僅僅是在擴展你自己寫的類時,這沒什么問題,但是當你在擴展標準的Cocoa?或者Cocoa Touch類的時候,它可能會導致嚴重的問題。 打開Album+TableRepresentation.m,增加下面的方法: Objective-c代碼?? -?(NSDictionary*)tr_tableRepresentation?? {?? ????return?@{@"titles":@[@"Artist",?@"Album",?@"Genre",?@"Year"],?? ?????????????@"values":@[self.artist,?self.title,?self.genre,?self.year]};?? }?? ?
咋們稍停片刻來看看這個模式的強大之處: 1.?你可以直接使用Album的屬性 2.?你不需要子類化就可以增加方法。當然如果你想子類化Album,你任然可以那樣做。 3.?簡簡單單的幾句代碼就讓你在不修改Album的情況下,返回了一個UITableView風格的Album。
蘋果在Foundation類中大量使用了Category。想知道他們是怎么做的,你可以代開NSString.h文件,找到@interface NSString,你將看到類和其它三個類別的定義:NSStringExtensionMethods,NSExtendedStringPropertyListParsing,NSStringDeprecated.類別讓方法組織成相互獨立的部分。?
Delegation(委托) 委托作為另外一個裝飾器模式,它是一種和其它對象交互的機制。舉例來說,當你使用UITableView的時候,你必須要實現tableView:numberOfRowsInSection:方法。 你不可能讓UITableView知道它需要在每個區域顯示多少行,因為這些是應用特定的數據。因此計算每個區域需要顯示多少行的職責就給了UITableView的委托。這就讓UITableView類獨立于它要顯示的數據。
這里通過一個圖來解釋當你創建UITableView的時候會發生什么:
? UITableView的職責就是顯示一個表格視圖。然而最終它需要一些它自身沒有的信息。那么它就求助于它的委托,通過發送消息給委托來獲取信息。在Objective-C實現委托模式的時候,一個類可以通過協議(Protocol)來聲明可選以及必要的方法。本指南稍后會涉及協議方面的內容。 子類化一個對象,復寫需要的方法看起來好像更容易一點,但是考慮到你只能子類化一個類,如果你想一個對象作為兩個或者更多對象的委托的話,使用子類化將不能實現。
注意:這個是一個重要的模式。蘋果在UIKit類中大量使用了它:UITableView,?UITextView,UITextField,?UIWebView,?UIAlert,?UIActionSheet,?UICollectionView,UIPickerView,UIGestureRecognizer,?UIScrollView等等等。 如何使用委托模式 打開ViewController.m文件,在文件開頭增加下面的導入語句: Objective-c代碼??#import?"LibraryAPI.h"?? ???#import?"Album+TableRepresentation.h"?? ?
現在,給類的擴展增加如下的私有變量,最終類的擴展如下所示: Objective-c代碼??@interfaceViewController?()?{?? ????UITableView?*dataTable;?? ????NSArray?*allAlbums;?? ????NSDictionary?*currentAlbumData;?? ????int?currentAlbumIndex;?? ???}?? ??? ???@end?? ?
然后用下面的代碼取代類型擴展中@interface一行:
Objective-c代碼??@interface?ViewController?()?<UITableViewDataSource,?UITableViewDelegate>?{?? ? 這就是如何使得委托符合協議,你可以把它認為是委托履行協議方法契約的約定。在這里,你指定ViewController將實現UITableViewDataSource和UITableViewDelegate協議。這種方式使得UITableView非常確定那些委托必須要實現的方法。 接下來,用如下代碼取代viewDidLoad方法: Objective-c代碼?? -?(void)viewDidLoad?? {?? ????[super?viewDidLoad];?? ????//?1?? ????self.view.backgroundColor?=?[UIColor?colorWithRed:0.76f?green:0.81f?blue:0.87f?alpha:1];?? ????currentAlbumIndex?=?0;?? ??? ????//2?? ????allAlbums?=?[[LibraryAPI?sharedInstance]?getAlbums];?? ??? ????//?3?? ????//?the?uitableview?that?presents?the?album?data?? ????dataTable?=?[[UITableView?alloc]?initWithFrame:CGRectMake(0,?120,?self.view.frame.size.width,?self.view.frame.size.height-120)?style:UITableViewStyleGrouped];?? ????dataTable.delegate?=?self;?? ????dataTable.dataSource?=?self;?? ????dataTable.backgroundView?=?nil;?? ????[self.view?addSubview:dataTable];?? }?? ?
下面我們來解釋一下上面代碼中標記了數字的地方: 1.?改變背景色為漂亮的藏青色 2.?通過API獲取專輯數據。你不需要直接使用PersistencyManager。 3.?創建UITableView,聲明viewController為UITableView的委托和數據源;因此viewController將提供所有的被UITableView需要的信息。
現在,在ViewController.m中增加如下的方法:?
Objective-c代碼??-?(void)showDataForAlbumAtIndex:(int)albumIndex?? {?? ????//?defensive?code:?make?sure?the?requested?index?is?lower?than?the?amount?of?albums?? ????if?(albumIndex?<?allAlbums.count)?? ????{?? ????????//?fetch?the?album?? ????????Album?*album?=?allAlbums[albumIndex];?? ????????//?save?the?albums?data?to?present?it?later?in?the?tableview?? ????????currentAlbumData?=?[album?tr_tableRepresentation];?? ????}?? ????else?? ????{?? ????????currentAlbumData?=?nil;?? ????}?? ??? ????//?we?have?the?data?we?need,?let's?refresh?our?tableview????? ????[dataTable?reloadData];?? }?? ?
showDataForAlbumAtIndex:從專輯數組中獲取需要的專輯數據。當你想去顯示新的數據的時候,你僅僅需要調用reloadData.這將使得UITableView去問委托一些如下的信息:表格視圖有多少個區域,每個區域應該顯示多少行,每個單元格長什么樣。
在viewDidLoad最后增加下面一行代碼:
Objective-c代碼??[self?showDataForAlbumAtIndex:currentAlbumIndex];?? ? 上面的代碼會在app啟動的時候加載當前的專輯,因為currentAlbumIndex設置為0,所以它將顯示第一個專輯。 構建并運行你的工程;你將得到一個崩潰信息,在調試控制臺上面將顯示如下的異常:
? 這里出了什么問題?你聲明ViewController作為UITableView的委托和數據源,但是這樣做了,也就意味著你必須實現所必須的方法-這些方法包括你還沒有實現的tableView:numberOfRowsInSection:方法.?
在ViewController.m文件中@implementation?和@end?之間的任何位置,增加下面的代碼: Objective-c代碼??-?(NSInteger)tableView:(UITableView?*)tableView?numberOfRowsInSection:(NSInteger)section?? {?? ????return?[currentAlbumData[@"titles"]?count];?? }?? ??? -?(UITableViewCell*)tableView:(UITableView?*)tableView?cellForRowAtIndexPath:(NSIndexPath?*)indexPath?? {?? ????UITableViewCell?*cell?=?[tableView?dequeueReusableCellWithIdentifier:@"cell"];?? ????if?(!cell)?? ????{?? ????????cell?=?[[UITableViewCell?alloc]?initWithStyle:UITableViewCellStyleValue1?reuseIdentifier:@"cell"];?? ????}?? ??? ????cell.textLabel.text?=?currentAlbumData[@"titles"][indexPath.row];?? ????cell.detailTextLabel.text?=?currentAlbumData[@"values"][indexPath.row];?? ??? ????return?cell;?? }?? ?
tableView:numberOfRowsInSection:方法返回表格視圖需要顯示的行數。這個和數據結構中的標題數量是匹配的。
tableView:cellForRowAtIndexPath:創建并返回一個包含標題和標題值的的單元格。 構建并運行你的工程,你的app應該會正常啟動并顯示下圖所示的畫面:
? 到目前為止進展挺順利。但是你回憶第一張本app最終效果的圖,你會發現在屏幕頂部有一個水平滾動視圖在不同的專輯之間切換。并不是構建一個只為這次使用的單一目的的水平滾動視圖,你為什么不做一個可以讓任何視圖復用的滾動視圖呢? 為了使這個視圖可以復用,應該由委托來決定所有的從左邊開始依次到下一個對象的內容。水平滾動條應該聲明那些能和它一起工作的委托方法,這有點類似UITableView的委托方法的方式。我們將在討論下一個模式的時候來實現它。
由 @krq_tiger(http://weibo.com/xmuzyq)翻譯,如果你發現有什么錯誤,請與我聯系謝謝。 門面(Facade)模式(譯者注:facade有些書籍譯為門面,有些書籍譯為外觀,此處譯為門面)
?
?
?
?
門面模式針對復雜的子系統提供了單一的接口,不需要暴漏一些列的類和API給用戶,你僅僅暴漏一個簡單統一的API。 下面的圖解釋了這個概念:?
?
?
這個API的使用者完全不需要關心背后的復雜性。這個模式非常適合有一大堆很難使用或者理解的類的情況。 門面模式解耦了使用系統的代碼和需要隱藏的接口和實現類。它也降低了外部代碼對內部子系統的依賴性。當隱藏在門面之后的類很容易發生變化的時候,此模式就很有用了,因為當背后的類發生變化的時候,門面類始終保持了同樣的API。 舉個例子來說,如果有一天你想取代后端服務,你不需要改變API的使用者,因為API沒有發生變化。?
如何使用門面模式?
當前你已經用PersistencyManager本地保存專輯數據,使用HTTPClient處理遠程連接,工程中的其它類暫時與本次實現的邏輯無關。 為了實現這個模式,只有LibraryAPI應該保存PersistencyManager和HTTPClient的實例,然后LibraryAPI將暴漏一個簡單的API去訪問這些服務。?
注意:?通常來說,單例類的生命周期貫穿于整個應用的生命周期中,你不應對保存太多其它對象的強引用,因為他們只有到應用關閉的時候才能被釋放。?
本次設計看起來像下圖:?
?
?
?
LibraryAPI將暴漏給其它代碼,但是它隱藏了HTTPClient和PersistencyManager的復雜性。?
打開LibraryAPI.h,在文件頭部增加下面的導入語句:?
Objective-c代碼???
Objective-c代碼???
目前有一些你需要暴漏給其它類的方法。 打開LibraryAPI.m,增加如下的兩個導入語句:?
Objective-c代碼???
這里將是唯一的導入這兩個類的地方。記住:你的API是對于復雜系統唯一的訪問點。 現在,增加通過類擴展(class extension)增加一些私有的變量(在@implementation?行之上):?
Objective-c代碼???
isOnline決定了是否服務器中任何專輯數據的改變應該被更新,例如增加或者刪除專輯。 你現在需要通過init初始化這些變量。在LibraryAPI.m中增加如下的代碼:?
Objective-c代碼???
?
HTTP?客戶端實際上不會真正的和一個服務器交互,它在這里僅僅是用來演示門面模式的使用,所以isOnline將總是NO。 接下來,增加如下的三個方法到LibraryAPI.m:?
Objective-c代碼???
我們來看一看addAlbum:atIndex:.這個類首先更新本地的數據,然后如果有網絡連接,它更新遠程服務器。這就是門面模式的強大之處。當某些外部的類增加一個新的專輯的時候,它不知道也不需要知道背后的復雜性。?
注意:當為子系統的類設計門面的時候,要記住:任何東西都不能阻止客戶端直接訪問這些隱藏的類。不要對這些防御性的代碼太過于吝嗇,并且也不要假設所有的客戶端都會和門面一樣使用你的類。?
構建并運行你的應用。你將看到一個激動人心的空白的黑屏(哈哈):?
?
?
接下來,你將需要在屏幕上顯示專輯數據,使用你的下個設計模式-裝飾器設計模式將是非常好的選擇。 裝飾器(Decorator)模式 裝飾器模式在不修改原來代碼的情況下動態的給對象增加新的行為和職責,它通過一個對象包裝被裝飾對象的方法來修改類的行為,這種方法可以做為子類化的一種替代方法。 在Objective-C中,存在兩種非常常見的實現:Category(類別)和Delegation(委托)。 Category(類別)Category(類別)是一種不需要子類化就可以讓你能動態的給已經存在的類增加方法的強有力的機制。新增的方法是在編譯期增加的,這些方法執行的時候和被擴展的類的其它方法是一樣的。它可能與裝飾器設計模式的定義稍微有點不同,因為Category(類別)不會保存被擴展類的引用。 注意:你除了可以擴展你自己的類以外,你還可以給Cocoa自己的類增加方法。?
如何使用類別 設想一種情況,你需要讓Album(專輯)對象顯示在一個表格視圖(TableView)中:
? 專輯的標題從何而來?因為專輯是模型對象,它本身不需要關心你如何顯示它的數據。你需要增加一些代碼去擴展專輯類的行為,但是不需要直接修改專輯類。 你將創建一個專輯類擴展的類別,它將定義一個新的方法,這個方法會返回能很容易和UITableViews使用的數據結構。這個數據結構如下圖所示:
? 為了給Album增加一個類別,導航到“File\New\File...\",選擇Objective-C category模板,不要習慣性的選擇Objective-C class模板。在Category域輸入TableRepresentation,Category on域輸入Album. 注意:你已經注意到了新建文件的名字了嗎?Album+TableRepresentation意味著你正在擴展Album類。這種約定是非常重要,因為它方便閱讀以及阻止和你或者其他人創建的類別沖突。 打開Album+TableRepresentation.h類,新增如下的方法原型: Objective-c代碼??
咋們稍停片刻來看看這個模式的強大之處: 1.?你可以直接使用Album的屬性 2.?你不需要子類化就可以增加方法。當然如果你想子類化Album,你任然可以那樣做。 3.?簡簡單單的幾句代碼就讓你在不修改Album的情況下,返回了一個UITableView風格的Album。
蘋果在Foundation類中大量使用了Category。想知道他們是怎么做的,你可以代開NSString.h文件,找到@interface NSString,你將看到類和其它三個類別的定義:NSStringExtensionMethods,NSExtendedStringPropertyListParsing,NSStringDeprecated.類別讓方法組織成相互獨立的部分。?
Delegation(委托) 委托作為另外一個裝飾器模式,它是一種和其它對象交互的機制。舉例來說,當你使用UITableView的時候,你必須要實現tableView:numberOfRowsInSection:方法。 你不可能讓UITableView知道它需要在每個區域顯示多少行,因為這些是應用特定的數據。因此計算每個區域需要顯示多少行的職責就給了UITableView的委托。這就讓UITableView類獨立于它要顯示的數據。
這里通過一個圖來解釋當你創建UITableView的時候會發生什么:
? UITableView的職責就是顯示一個表格視圖。然而最終它需要一些它自身沒有的信息。那么它就求助于它的委托,通過發送消息給委托來獲取信息。在Objective-C實現委托模式的時候,一個類可以通過協議(Protocol)來聲明可選以及必要的方法。本指南稍后會涉及協議方面的內容。 子類化一個對象,復寫需要的方法看起來好像更容易一點,但是考慮到你只能子類化一個類,如果你想一個對象作為兩個或者更多對象的委托的話,使用子類化將不能實現。
注意:這個是一個重要的模式。蘋果在UIKit類中大量使用了它:UITableView,?UITextView,UITextField,?UIWebView,?UIAlert,?UIActionSheet,?UICollectionView,UIPickerView,UIGestureRecognizer,?UIScrollView等等等。 如何使用委托模式 打開ViewController.m文件,在文件開頭增加下面的導入語句: Objective-c代碼??
現在,給類的擴展增加如下的私有變量,最終類的擴展如下所示: Objective-c代碼??
然后用下面的代碼取代類型擴展中@interface一行:
Objective-c代碼??
下面我們來解釋一下上面代碼中標記了數字的地方: 1.?改變背景色為漂亮的藏青色 2.?通過API獲取專輯數據。你不需要直接使用PersistencyManager。 3.?創建UITableView,聲明viewController為UITableView的委托和數據源;因此viewController將提供所有的被UITableView需要的信息。
現在,在ViewController.m中增加如下的方法:?
Objective-c代碼??
showDataForAlbumAtIndex:從專輯數組中獲取需要的專輯數據。當你想去顯示新的數據的時候,你僅僅需要調用reloadData.這將使得UITableView去問委托一些如下的信息:表格視圖有多少個區域,每個區域應該顯示多少行,每個單元格長什么樣。
在viewDidLoad最后增加下面一行代碼:
Objective-c代碼??
? 這里出了什么問題?你聲明ViewController作為UITableView的委托和數據源,但是這樣做了,也就意味著你必須實現所必須的方法-這些方法包括你還沒有實現的tableView:numberOfRowsInSection:方法.?
在ViewController.m文件中@implementation?和@end?之間的任何位置,增加下面的代碼: Objective-c代碼??
tableView:numberOfRowsInSection:方法返回表格視圖需要顯示的行數。這個和數據結構中的標題數量是匹配的。
tableView:cellForRowAtIndexPath:創建并返回一個包含標題和標題值的的單元格。 構建并運行你的工程,你的app應該會正常啟動并顯示下圖所示的畫面:
? 到目前為止進展挺順利。但是你回憶第一張本app最終效果的圖,你會發現在屏幕頂部有一個水平滾動視圖在不同的專輯之間切換。并不是構建一個只為這次使用的單一目的的水平滾動視圖,你為什么不做一個可以讓任何視圖復用的滾動視圖呢? 為了使這個視圖可以復用,應該由委托來決定所有的從左邊開始依次到下一個對象的內容。水平滾動條應該聲明那些能和它一起工作的委托方法,這有點類似UITableView的委托方法的方式。我們將在討論下一個模式的時候來實現它。
轉載于:https://www.cnblogs.com/sanjianghuiliu/p/3663918.html
總結
以上是生活随笔為你收集整理的IOS设计模式之二(门面模式,装饰器模式)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql dbutil_DBUtil
- 下一篇: 设计模式(10)-----模板方法模式