一 ?iOS中都有什么設計模式?
1.代理模式
2.觀察者模式
3.MVC模式
4.單例模式
5.策略模式
6.工廠模式
二 ?各個設計模式的作用?
(一)代理模式
在觀察者模式中,一個對象任何狀態的變更都會通知另外的對改變感興趣的對象。這些對象之間不需要知道彼此的存在,這其實是一種松耦合的設計。當某個屬性變化的時候,我們通常使用這個模式去通知其它對象。
此模式的通用實現中,觀察者注冊自己感興趣的其它對象的狀態變更事件。當狀態發生變化的時候,所有的觀察者都會得到通知。蘋果的推送通知(Push Notification)就是一個此模式的例子。
如果你要遵從MVC模式的概念,你需要讓模型對象和視圖對象在不相互直接引用的情況下通信。這正是觀察者模式的用武之地。
Cocoa通過通知(Notifications)和Key-Value Observing(KVO)來實現觀察者模式。
在cocoa框架中的Delegate模式中,委托人往往是框架中的對象(視圖中的控件、表視圖神馬的),代理人往往是視圖控制器對象。
在我們這個例子中UITableView是委托人,代理人首先得滿足一個條件:就是在.h文件中申明它擁有代理資格:
@interface WhateverViewController < UITableViewDelegate >
@end
紅色的表示這個視圖控制器擁有UITableView的代理資格。
其次,在.m文件中定義委托人可以讓代理人去代替做的事情:
//視圖控制器來代辦應該有多少個節- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {? ? return [NSArray count];}
//視圖控制器來代辦某個節應該有多少行- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {? ? return [[NSArray objectAtIndex:section]count];}// 視圖控制器來代辦負責每個欄格的外觀- (UITableViewCell *)tableView:(UITableView *)tableView? ? ? cellForRowAtIndexPath:(NSIndexPath *)indexPath {? ? static NSString *CellIdentifier = @"Cell";? ? UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];? ? if (cell == nil) {? ? ? ? cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault? ? ? ? ? ? ? ? reuseIdentifier:CellIdentifier] autorelease];? ? }? ? cell.textField.text = [NSArray objectAtIndex:indexPath.row];? ? ? return cell;}//負責當欄格被點擊后需要觸發的事件- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {? ? AnotherViewController *anotherViewController = [[AnotherViewController alloc]? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? initWithNibName:@"AnotherView" bundle:nil];? ? [self.navigationController pushViewController:anotherViewController];? ? [anotherViewController release];}// 這個是可選的,視圖控制器勤快我就幫你代辦,不勤快我就可以不幫你辦這事兒,(提供哪些個行可以被編輯)- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {? ? return YES;}
// 對特定編輯風格進行操作- (void)tableView:(UITableView *)tableView? ? ? ? commitEditingStyle:(UITableViewCellEditingStyle)editingStyle? ? ? ? forRowAtIndexPath:(NSIndexPath *)indexPath {? ? if (editingStyle == UITableViewCellEditingStyleDelete) {? ? ? ? [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];? ? }? ? else if (editingStyle == UITableViewCellEditingStyleInsert) {? ? }}
// 可選,對那些被移動欄格作特定操作- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath? ? ? ? toIndexPath:(NSIndexPath *)toIndexPath {}
// 對那些可以移動的行返回YES- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {? ? // 如果不想讓欄格移動,就返回NO? ? return YES;}
好了,當這個委托人需要辦這些事時,代理人自己就站出來幫忙辦了。這就是ios中的Delegate模式。
二、自定義的delegate模式
@interface A:UIViewid transparendValueDelegate;@property(nomatic, retain) id transparendValueDelegate;@end@implementation A@synthesize transparendValueDelegate-(void)Call{ NSString* value = @"你好";[transparendValueDelegate transparendValue: value];}@end
@interface B:UIView
NSString* value;@end
@implementation B-(void)transparendValue:(NSString*)fromValue{value = fromValue;NSLog(@"%@ ,我是B",value); }@end
使用時:
A* a = [[A alloc] init];
B* b = [[B alloc] init];
a. transparendValueDelegate = b;//設置A代理委托對象為B
[a Call];
這樣就會輸出:
你好,我是B
委托模式關鍵就在于一個“被”字。這個B是很被動的,隨時就會被你A Call一下。
三、為什么會有delegate模式
換句話說,它可以用來解決神馬問題?
當一個類的某些功能需要被別人來實現,但是既不明確是些什么功能,又不明確誰來實現這些功能的時候,委托模式就可以派上用場。
例如你可以再寫個C類,實現-(void)transparendValue:(NSString*)fromValue {NSLog(@"%@ ,我是C",value); }也是完全可以的。換誰來,只要它實現了這個方法,我就可以委托它來做這個事。
說到底一切都是為了使類之間的耦合性更松散。好的代碼應該對擴展開放,對修改關閉。
總結來自http://blog.sina.com.cn/s/blog_b638dc89010192qu.html
(二)觀察者模式
在軟件開發中,無論是那種高級語言中總會伴隨著一些最為常用的設計模式,即便就如iOS開發中與我們打交道最多的無非就是單例模式、觀察者模式和工廠模式了,當然了其他的設置模式也同樣存在在編程的很多地方。下面就就讓我們簡單的了解下觀察者模式吧!
觀察者模式本質上時一種發布-訂閱模型,用以消除具有不同行為的對象之間的耦合,通過這一模式,不同對象可以協同工作,同時它們也可以被復用于其他地方Observer從Subject訂閱通知,ConcreteObserver實現重現ObServer并將其重載其update方法。一旦SubJect的實例需要通知Observer任何新的變更,Subject會發送update消息來通知存儲在其內部類中所注冊的Observer、在ConcreteObserver的update方法的實際實現中,Subject的內部狀態可被取得并進行后續處理。其類圖如下:
觀察者模式.png
由上面我們可以發現觀察者模式無非在是定義對象間的一種一對多的依賴關系,并且當一個對象的狀態發生改變的時候,所有依賴于它的對象都會得到通知且自動更新。即如果Subject允許其他觀察者(實現了觀察者接口的對象)對這個Subject的改變進行請閱,當Subject發送了變化,那么Subject會將這個變化發送給所有的觀察者,觀察者就能對Subject的變化做出更新。其時序圖如下
觀察者模式2.png
通過上面的觀察我們可以發現如果用N個Observer來拓展Subject的行為,這些Observer具有處理存儲在Subject中的信息的特定實現,這樣也就實現了前面所說的消除不同對象間的耦合的功能了。
那么了解了這些我們可能就會更像了解下我們在什么時候才會去使用觀察者模式呢?
當需要將改變通知所有的對象時,而你又不知道這些對象的具體類型
改變發生在同一個對象中,并需要改變其他對象將相關的狀態進行更新且不知道有多少個對象。
而同樣的在我們日常的開發中在Cocoa Touch框架中的的兩種經常打交道的技術KVO與通知都實現了觀察者模式,所以下面我們討論的重點也就是基于這兩個方面的。
通知
在之前的博文中曾經簡單的提到過一些通知的基礎使用方法,所以一些基本的使用方法再次就不贅述。言歸正傳,在Cocoa Touch框架中NSNotificationCenter和NSNotification對象實現了一對多的模型。通過NSNotificationCenter可以讓對象之間進行通訊,即便這些對象之間并不認識。下面我們來看下NSNotificationCenter發布消息的方法:
NSNotification? * subjectMessage = [NSNotification? notificationWithName:@"subjectMessage"? object:self];NSNotificationCenter? * notificationCenter = [NSNotificationCenter? defaultCenter];
[notificationCenter postNotification:subjectMessage];
通過上面的代碼我們創建了一個名為subjectMessage的NSNotification對象,然后通過notificationCenter來發布這個消息。通過向NSNotificationCenter類發送defaulCenter消息,可以得到NSNotificationCenter實例的引用。每個進程中只有一個默認的通知中心,所以默認的NSNotificationCenter是個單例對象。如果有其他觀察者定于了其對象的相關事件則可以通過以下的方法來進行操作:
NSNotificationCenter? * notificationCenter1 = [NSNotificationCenter? defaultCenter];? ? [notificationCenter addObserver:self? selector:@selector(update:) name:@"subjectMessage"? object:nil ];
經過以上步驟我們已經向通知中心注冊了一個事件并且通過selector制定了一個方法update:下面我們可以實現以下這個方法
- (void)update:(NSNotification*)notification{if ([[notification name] isEqualToString:@"subjectMessage"]) {NSLog(@"%@",@"猴子派來的救兵去哪了?");
}
}
當然最后如果我們需要對監聽進行銷毀
- (void)dealloc {? ? [[NSNotificationCenter defaultCenter] removeObserver:self];
}
了解過通知之后我們來看一下KVO
KVO是Cocoa提供的一種稱為鍵值觀察的機制,對象可以通過它得到其他對象特定屬性的變更通知。而這個機制是基于NSKeyValueObserving非正式些,Cocoa通過這個協議為所有遵循協議的對象提供了一種自動化的屬性監聽的功能。
雖然通知和KVO都可以對觀察者進行實現,但是他們之間還是略有不同的,由上面的例子我們可以看出通知是由一個中心對象為所有觀察者提供變更通知,主要是廣義上關注程序事件,而KVO則是被觀察的對象直接想觀察者發送通知,主要是綁定于特定對象屬性的值。下面我們通過一個簡單的例子來了解下他的一些是使用方法
首先我們有Hero這個模型
@property (nonatomic,copy)NSString * name;@property (nonatomic,copy)NSString * title;@property (nonatomic,assign)NSUInteger age;
在控制其中我們將其初始化并賦值
self.hero = [[Hero alloc]init];self.hero.name = @"趙云";self.hero.title = @"將軍";self.hero.age =87;
現在我們的這個對象基本有值了,那么我們將這個對象的name監聽下他的改變
[self.hero addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
觸發通知并將值改變
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{self.hero.name =@"張飛";
}
在制定的回調函數中,處理收到的更改通知
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{if([keyPath isEqualToString:@"name"])? ? {NSLog(@"賦值后--%@",self.hero.name);NSLog(@"新的值--%@",change[@"new"]);NSLog(@"以前的值--%@",change[@"old"]);
}
}
回調打印如下:
dayin.png
最后注銷觀察者
- (void)dealloc{? ? [self.hero removeObserver:self forKeyPath:@"name"];
}
到了這里觀察者模式中常用的KVO及通知的內容就到這里,不過要知道這里談及的只是最基礎的用法,后面我們可能還是有更加深入的探究,或者在后續中可能還會對比iOS中的代理以及Block來探尋下iOS中的消息傳遞機制,再或者像Swift中的didSet、willSet的屬性監聽的方法,這些都是很好玩的內容,不是么?
(三)MVC模式
MVC根據角色劃分類,涉及到三個角色:
Model:模型保存應用程序的數據。
Controller:控制器是一個協調所有工作的中介者。它訪問模型中的數據并在視圖中展示它們,同時它們還監聽事件和操作數據。
一個MVC模式的好的實現也就意味著每一個對象都會被劃分到上面所說的組中。
我們可以很好的用下圖來描述通過控制器實現的視圖到模型的交互過程:
模型會把任何數據的變更通知控制器,然后控制器更新視圖數據。視圖對象通知控制器用戶的操作,控制器要么根據需要來更新模型,要么檢索任何被請求的數據。
你可能在想為什么不能僅僅使用控制器,在一個類中實現視圖和模型,這樣貌似更加容易?
所有的這些都歸結于代碼關注點分離以及復用。在理想的狀態下,視圖應該和模型完全的分離。如果視圖不依賴某個實際的模型,那么視圖就可以被復用來展示不同模型的數據。
舉個例子來說,如果將來你打算加入電影或者書籍到你的資料庫中,你仍然可以使用同樣的AlbumView去顯示電影和書籍數據。更進一步來說,如果你想創建一個新的與專輯有關聯的工程,你可以很簡單的復用Album類,因為它不依賴任何視圖。這就是MVC的強大之處。
(四)單例模式
單例設計模式確保對于一個給定的類只有一個實例存在,這個實例有一個全局唯一的訪問點。它通常采用懶加載的方式在第一次用到實例的時候再去創建它。
注意:蘋果大量使用了此模式。例如:[NSUserDefaults standardUserDefaults], [UIApplication sharedApplication], [UIScreen mainScreen], [NSFileManager defaultManager],所有的這些方法都返回一個單例對象。
你很可能會想為什么這么關心是否一個類有多個實例?畢竟代碼和內存都是廉價的,對嗎?
有一些情況下,只有一個實例顯得非常合理。舉例來說,你不需要有多個Logger的實例,除非你想去寫多個日志文件。或者一個全局的配置處理類:實現線程安全的方式訪問共享實例是容易的,比如一個配置文件,有好多個類同時修改這個文件。
(五)策略模式
1.概述
在軟件開發中也常常遇到類似的情況,實現某一個功能有多種算法或者?策略,我們可以根據環境或者條件的不同選擇不同的算法或者策略來完成該功能?。如查找、排序等,一種常用的方法是硬編碼(Hard Coding)在一個類中,如需要提供多種查找算法,可以將這些算法寫到一個類中,在該類中提供多個方法,每一個方法對應一個具體的查找算法;當然也可以將這些查找算法封裝在一個統一的方法中,通過if…else…或者?case?等條件判斷語句來進行選擇。這兩種實現方法我們都可以稱之為硬編碼,如果需要增加一種新的查找算法,需要修改封裝算法類的源代碼;更換查找算法,也需要修改客戶端調用代碼。在這個算法類中封裝了大量查找算法,?該類代碼將較復雜,維護較為困難。如果我們將這些策略包含在客戶端?,這種做法更不可取,將導致客戶端程序龐大而且難以維護,如果存在大量可供選擇的算法時問題將變得更加嚴重。
例子1:一個菜單功能能夠根據用戶的“皮膚”首選項來決定是否采用水平的還是垂直的排列形式。同事可以靈活增加菜單那的顯示樣式。
例子2:出行旅游:我們?可以有幾個策略可以考慮:可以騎自行車,汽車,做火車,飛機。每個策略都可以得到相同的結果,但是它們使用了不同的資源。選擇策略的依據是費用,時間,使用工具還有每種方式的方便程度 。
2.問題
如何讓算法和對象分開來,使得算法可以獨立于使用它的客戶而變化?
3.解決方案
策略模式:?定義一系列的算法,把每一個算法封裝起來, 并且使它們可相互替換。本模式使得算法可獨立于使用它的客戶而變化。也稱為?政策模式?(Policy)。(?Definea family of algorithms,encapsulate each one, andmake them interchangeable. Strategy lets the algorithmvary independently from clients that use it.??)
策略模式把對象本身和運算規則區分開來,其?功能非常強大,因為這個設計模式本身的核心思想就是面向對象編程的多形性的思想。
4.適用性
當存在以下情況時使用Strategy模式
1)? 許多相關的類僅僅是行為有異。 “策略”提供了一種用多個行為中的一個行為來配置一個類的方法。即一個系統需要動態地在幾種算法中選擇一種。
2)? 需要使用一個算法的不同變體。例如,你可能會定義一些反映不同的空間 /時間權衡的算法。當這些變體實現為一個算法的類層次時 ,可以使用策略模式。
3)? 算法使用客戶不應該知道的數據。可使用策略模式以避免暴露復雜的、與算法相關的數據結構。
4)? 一個類定義了多種行為 , 并且這些行為在這個類的操作中以多個條件語句的形式出現。將相關的條件分支移入它們各自的Strategy類中以代替這些條件語句。
5.結構
6.效果
Strategy模式有下面的一些優點:
1) 相關算法系列
Strategy類層次為Context定義了一系列的可供重用的算法或行為。?繼承有助于析取出這些算法中的公共功能。
2) 提供了可以替換繼承關系的辦法
: 繼承提供了另一種支持多種算法或行為的方法。你可以直接生成一個Context類的子類,從而給它以不同的行為。但這會將行為硬行編制到 Context中,而將算法的實現與Context的實現混合起來,從而使Context難以理解、難以維護和難以擴展,而且還不能動態地改變算法。最后你得到一堆相關的類 , 它們之間的唯一差別是它們所使用的算法或行為。?將算法封裝在獨立的Strategy類中使得你可以獨立于其Context改變它,使它易于切換、易于理解、易于擴展。
3) 消除了一些if else條件語句
:Strategy模式提供了用條件語句選擇所需的行為以外的另一種選擇。當不同的行為堆砌在一個類中時 ,很難避免使用條件語句來選擇合適的行為。將行為封裝在一個個獨立的Strategy類中消除了這些條件語句。含有許多條件語句的代碼通常意味著需要使用Strategy模式。
4) 實現的選擇
Strategy模式可以提供相同行為的不同實現。客戶可以根據不同時間 /空間權衡取舍要求從不同策略中進行選擇。
Strategy模式缺點:
1)客戶端必須知道所有的策略類,并自行決定使用哪一個策略類
: ?本模式有一個潛在的缺點,就是一個客戶要選擇一個合適的Strategy就必須知道這些Strategy到底有何不同。此時可能不得不向客戶暴露具體的實現問題。因此僅當這些不同行為變體與客戶相關的行為時 , 才需要使用Strategy模式。
2 ) Strategy和Context之間的通信開銷
:無論各個ConcreteStrategy實現的算法是簡單還是復雜, 它們都共享Strategy定義的接口。因此很可能某些 ConcreteStrategy不會都用到所有通過這個接口傳遞給它們的信息;簡單的 ConcreteStrategy可能不使用其中的任何信息!這就意味著有時Context會創建和初始化一些永遠不會用到的參數。如果存在這樣問題 , 那么將需要在Strategy和Context之間更進行緊密的耦合。
3 )策略模式將造成產生很多策略類
:可以通過使用享元模式在一定程度上減少對象的數量。 增加了對象的數目 Strategy增加了一個應用中的對象的數目。有時你可以將 Strategy實現為可供各Context共享的無狀態的對象來減少這一開銷。任何其余的狀態都由 Context維護。Context在每一次對Strategy對象的請求中都將這個狀態傳遞過去。共享的 Strategy不應在各次調用之間維護狀態。
7. iOS應用分析
例如,我們在驗證用戶輸入的表單的時候,加入包括電話輸入框的驗證和郵件輸入框的驗證,這兩部分的驗證算法是不同的,如果把這個算法看成一個函數,他幾乎有相同的輸入參數和返回參數。我們可以把這個相同的函數可以抽象為基類(InputValidator)的一個方法(bool validateInput(input,error)),然后抽象出兩個具體的策略類:電話驗證類(PhoneValidator)和郵件驗證類(EmailValidator),他們需要在各自的實現里面去復寫父類的驗證方法。為了能夠正常的調用到驗證類的驗證方法,我們需要自定義一個UITextField的子類CustomTextField,其中有一個InputValidator類型的引用和一個validate方法,該方法里面調用InputValidator的驗證方法,然后在textFieldDidEndEditing代理方法里面調用CustomTextField的validate方法,這樣就不用我們在判斷輸入是否合法的時候通過if else去處理每種邏輯,而且這樣做方便擴展,提高可復用性。
實例:排序算法,NSArray的sortedArrayUsingSelector;經典的鴨子會叫,會飛案例。
(六)工廠模式
工廠模式我的理解是:他就是為了創建對象的
創建對象的時候,我們一般是alloc一個對象,如果需要創建100個這樣的對象,如果是在一個for循環中還好說,直接一句alloc就行了,但是事實并不那么如意,我們可能會在不同的地方去創建這個對象,那么我們可能需要寫100句alloc 了,但是如果我們在創建對象的時候,需要在這些對象創建完之后,為它的一個屬性添加一個固定的值,比方說都是某某學校的學生,那么可能有需要多些100行重復的代碼了,那么,如果寫一個-(void)createObj方法,把創建對象和學校屬性寫在這個方法里邊,那么就是會省事很多,也就是說我們可以alloc 創建對象封裝到一個方法里邊,直接調用這個方法就可以了,這就是簡單工廠方法
代碼結構如下
Animal 類
@interface Animal :NSObject
@proterty(nonatomic,strong) NSString *name;
-(void)laugh;
@end
Dog類
@interface Dog:Animal
@end
Cat類
@interface?Cat:Animal
@end
創建對象的工廠類
.h
@interface?AnimalFactory:NSObject
+(Dog *)createDog;
+(Cat *)createCat;
@end
.m
@implementation AnimalFactory
+(Dog?*)createDog{
Dog *dog=[[Dog alloc]init];
dog.name=@“baby”;
return dog;
}
+(Cat?*)?createCat{
Cat?*cat=[[Cat?alloc]init];
return cat;
}
Main.m文件
Dog *dog=[AnimalFactory createDog];
Cat *cat=[AnimalFactory createCat];
這是簡單工廠模式
現在創建100個Dog對象,如果這100個對象寫在程序中的不同地方,按上邊的方法是需要把Dog *dog=[AnimalFactory createDog];這一句話寫在程序中很多不同的地方,那么現在有一個需求,就是如果需要把這些創建的100個Dog對象全部變成Cat對象,那么按照剛才的那個做法,就需要在這100句代碼中把createDog方法變成createCat方法了,這樣做還是很復雜
那么這個時候用工廠方法模式就能解決這個難題了
工廠方法模式是為每一個要創建的對象所在的類都相應地創建一個工廠
代碼如下
@interface?AnimalFactory:NSObject
-(Animal*)createAnimal;
@end;
Dog工廠類
@interface DogFactory:AnimalFactory;
@implementation DogFactory
-(Animal *)createAnimal{
retrurn [[Dog alloc]init];
}
@end
Cat工廠類
@interface?CatFactory:AnimalFactory;
@implementation?Cat?Factory
-(Animal *)createAnimal
retrurn [[Cat?alloc]init];
}
@end
Main.m
AnimalFactory *dogFactory=[[DogFactory alloc]init];
Animal *animal1=[dogFactory createAnimal];
[animal1 laugh];
Animal *animal2=[dogFactory createAnimal];
[animal2 laugh];
…….
Animal *animal100=[dogFactory createAnimal];
[animal100 laugh];
這樣話如果要把100個Dog改為Cat的話,只需要吧DogFactory改為CatFactory就可以了
但是工廠方法也有它的限制:
1.要創建的類必須擁有同一個父類
2.要創建的類在100個不同的地方所調用的方法必須一樣
以上這些只是個人感悟,會有一些不足的地方,請大家幫忙改正,嘿嘿