一你弦、OC中幾種常用的設(shè)計(jì)模式
1.單例模式(Singleton)
在iOS開發(fā)我們經(jīng)常碰到只需要某類一個(gè)實(shí)例的情況,如:
● UIApplication類提供了 +sharedAPplication方法創(chuàng)建和獲取UIApplication單例
● NSBundle類提供了 +mainBunle方法獲取NSBundle單例
● NSFileManager類提供了 +defaultManager方法創(chuàng)建和獲得NSFileManager單例。(PS:有些時(shí)候我們得放棄使用單例模式尸昧,使用-init方法去實(shí)現(xiàn)一個(gè)新的實(shí)例烹俗,比如使用委托時(shí))
● NSNotificationCenter提供了 +defaultCenter方法創(chuàng)建和獲取NSNotificationCenter單例(PS:該類還遵循了另一個(gè)重要的設(shè)計(jì)模式:觀察者模式)
● NSUserDefaults類提供了 +defaultUserDefaults方法去創(chuàng)建和獲取NSUserDefaults單例
如何實(shí)現(xiàn)單例模式
首先給大家介紹一下GCD技術(shù)萍程,是蘋果針對于多核CPU的多任務(wù)解決方案茫负。是一組基于C語言開發(fā)的API。GCD提供了一個(gè)dispatch_once函數(shù)置吓,這個(gè)函數(shù)的作用就是保證block里的語句在整個(gè)應(yīng)用的生命周期里只執(zhí)行一次缔赠。
實(shí)現(xiàn)代碼
#import "JasonDemo.h"
@implementation JasonDemo
+ (instancetype)sharedInstance {
static JasonDemo *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[JasonDemo alloc] init];
});
return sharedInstance;
}
@end
上述代碼中有5小步嗤堰,解釋如下:
聲明一個(gè)可以新建和獲取單個(gè)實(shí)例對象的方法
聲明一個(gè)static類型的類變量
聲明一個(gè)只執(zhí)行一次的任務(wù)
調(diào)用dispatch_once執(zhí)行該任務(wù)指定的代碼塊踢匣,在該代碼塊中實(shí)例化上文聲明的類變量
返回在整個(gè)應(yīng)用的生命周期中只會被實(shí)例化一次的變量
單例模式作用
由于單例模式在內(nèi)存中只有一個(gè)實(shí)例,減少了內(nèi)存開支后专,特別是一個(gè)對象需要頻繁地創(chuàng)建输莺、銷毀時(shí)嫂用,而且創(chuàng)建或銷毀時(shí)性能又無法優(yōu)化,單例模式的優(yōu)勢就非常明顯甘畅。
由于單例模式只生成一個(gè)實(shí)例,所以減少了系統(tǒng)的性能開銷蓄氧,當(dāng)一個(gè)對象的產(chǎn)生需要比較多的資源時(shí),如讀取配置缴淋、產(chǎn)生其他依賴對象時(shí),則可以通過在應(yīng)用啟動(dòng)時(shí)直接產(chǎn)生一個(gè)單例對象露氮,然后用永久駐留內(nèi)存的方式來解決(在Java EE中采用單例模式時(shí)需要注意JVM垃圾回收機(jī)制)畔规。
單例模式可以避免對資源的多重占用恨统,例如一個(gè)寫文件動(dòng)作,由于只有一個(gè)實(shí)例存在內(nèi)存中莫绣,避免對同一個(gè)資源文件的同時(shí)寫操作对室。
單例模式可以在系統(tǒng)設(shè)置全局的訪問點(diǎn)咖祭,優(yōu)化和共享資源訪問么翰,例如可以設(shè)計(jì)一個(gè)單例類,負(fù)責(zé)所有數(shù)據(jù)表的映射處理慧瘤。
舉個(gè)栗子
跳轉(zhuǎn)使用單例之前
跳轉(zhuǎn)管理基類
#import <Foundation/Foundation.h>
@class CHBaseController;
@interface CCInteractor : NSObject
{
}
@property (nonatomic, weak) CHBaseController* baseController;
@end
具體業(yè)務(wù)跳轉(zhuǎn)子類
@interface CHContactsInteractor : CCInteractor
- (void)goOrganization;
- (void)goMyDepartmentWithDeptID:(NSString *)deptID andDeptName:(NSString *)deptName;
@end
使用
@property (nonatomic, strong) CHContactsInteractor *contactsInteractor;
self.contactsInteractor = [CHContactsInteractor new];
[self.contactsInteractor goOrganization];
使用單例優(yōu)化以后
跳轉(zhuǎn)管理基類
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface ATModuleInteractor : NSObject
+ (instancetype)sharedInstance;
// 普通push
- (void)pushToVC:(UIViewController *)VC;
// 強(qiáng)制跳轉(zhuǎn)rootView再push
- (void)PushToChatVC:(UIViewController *)VC;
@end
具體業(yè)務(wù)跳轉(zhuǎn)管理分類
#import "ATModuleInteractor.h"
@interface ATModuleInteractor (CoordinationInteractor)
// 任務(wù)列表
- (void)goTaskList;
@end
使用
[[ATModuleInteractor sharedInstance] goTaskList];
2.觀察者模式
概念:操作對象向被觀察者對象投送消息锅减,使得被觀察者的狀態(tài)得以改變怔匣,在此之前已經(jīng)有觀察者向被觀察對象注冊,訂閱它的廣播金闽,現(xiàn)在被觀察對象將自己狀態(tài)發(fā)生改變的消息廣播出來代芜,觀察者接收到消息各自做出應(yīng)變浓利。
蘋果的Cocoa Touch框架中使用了觀察者模式的有:通知機(jī)制和KVO
2.1 通知(notification)機(jī)制
在通知機(jī)制中對某個(gè)通知感興趣的所有對象都可以成為接受者。首先嫡秕,這些對象需要向通知中心(NSNotificationCenter)發(fā)出addObserver:selector:name:object:消息進(jìn)行注冊昆咽,在投送對象投送通知送給通知中心時(shí)牙甫,通知中心就會把通知廣播給注冊過的接受者。所有的接受者不知道通知是誰投送的汇在,不去關(guān)心它的細(xì)節(jié)糕殉。投送對象和接受者是一對多的關(guān)系殖告。接受者如果對通知不再關(guān)注,會給通知中心發(fā)送removeObserver:name:Object:消息解除注冊羡洁,以后不再接受通知爽丹。
代碼嘗試
1.進(jìn)入后臺時(shí)粤蝎,發(fā)出通知
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[NSNotificationCenter defaultCenter]postNotificationName:@"APPTerminate" object:self];
}
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//注意此處的selector有參數(shù),要加冒號
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(doSomething:) name:@"APPTerminate" object:nil];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
[[NSNotificationCenter defaultCenter]removeObserver:self];
// Dispose of any resources that can be recreated.
}
#pragma mark -處理通知
-(void)doSomething:(NSNotification*)notification{
NSLog(@"收到進(jìn)入后臺通知");
}
@end
在APP退到后臺時(shí)秸应,發(fā)出廣播软啼,而viewController因?yàn)闀r(shí)觀察者,收到廣播锣披,執(zhí)行doSomething方法雹仿,打印出收到廣播的文字闪唆。
2.2 KVO(Key-Value-Observing)機(jī)制
該機(jī)制下觀察者的注冊是在被觀察者的內(nèi)部進(jìn)行的钓葫,不同于通知機(jī)制(由觀察者自己注冊),需要被觀察者和觀察者同時(shí)實(shí)現(xiàn)一個(gè)協(xié)議:NSKeyValueObserving帆调,被觀察者通過addObserver:forKeypath:options:context方法注冊觀察者番刊,以及要被觀察的屬性影锈。
實(shí)現(xiàn)代碼
被觀察類(由于繼承自NSObject,而NSObject已實(shí)現(xiàn)了NSKeyValueObserving協(xié)議,所以不需要做聲明)
#import <Foundation/Foundation.h>
@interface JWWatchModel : NSObject
@property (nonatomic, copy) NSString *JWName;
@property (nonatomic, copy) NSString *JWAge;
@end
觀察類
- (void)viewDidLoad {
[super viewDidLoad];
self.JWModel = [JWWatchModel new];
[self.JWModel addObserver:self forKeyPath:@"JWName" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"你考了幾分"];
UIBarButtonItem *changeBtn = [[UIBarButtonItem alloc] initWithTitle:@"戳我啊" style:UIBarButtonItemStylePlain target:self action:@selector(changeClick)];
[self.navigationItem setRightBarButtonItem:changeBtn];
// Do any additional setup after loading the view.
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"JWName"]) {
UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:(__bridge NSString *)context message:[self dictionaryToJson:change] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleCancel handler:nil];
[alterVC addAction:action];
[self presentViewController:alterVC animated:YES completion:nil];
NSLog(@"%@",change);
}
}
3.委托代理模式
委托模式就像是java中的接口,類可以實(shí)現(xiàn)或不實(shí)現(xiàn)協(xié)議(接口)中的方法辆床。通過此種方式,達(dá)到最大的解耦目的轿秧,方便項(xiàng)目的擴(kuò)展菇篡。不過你需要設(shè)置應(yīng)用的委托對象一喘,以確定協(xié)議中的方法為誰服務(wù)。
拿最常用的UITableViewDelegate UITableViewDataSource來舉例:
實(shí)現(xiàn)一個(gè)頁面有一個(gè)UItableView铝侵,UItableView的每一欄(cell)的數(shù)據(jù)由我們指定,那么我們該如何做呢狐赡?蘋果也自然想到了這一點(diǎn)颖侄,于是定義了一個(gè)接口享郊,這個(gè)接口有許多的方法,只需要我們把要服務(wù)的對象傳進(jìn)去展蒂,就可以使用這些方法了苔咪,這個(gè)接口就是委托和協(xié)議。而UITableViewDelegate 和 UITableViewDataSource 就是專為UITableView而寫的委托和協(xié)議箕般。用法如下:
#pragma mark - UITableView Delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.serviceDetail.payWayList.count;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CoustomerTableIdentifier = @"RecipeTableCellTableViewCell";
RecipeTableCellTableViewCell *cell =(RecipeTableCellTableViewCell *) [tableView dequeueReusableCellWithIdentifier:CoustomerTableIdentifier];
if (cell == nil) {
cell = [[RecipeTableCellTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CoustomerTableIdentifier];
}
recipe = [recipes objectAtIndex:indexPath.row];
cell.nameLabel.text = recipe.name;
cell.prepTimeLabel.text= recipe.prepTime;
cell.thumbnailImageView.image = [UIImage imageNamed:recipe.image];
return cell;}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 0.0001;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 0.0001;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ServicePayWay *payway = self.serviceDetail.payWayList[indexPath.row];
if ([self.selectedPayway.payWayCode isEqualToString:payway.payWayCode]) {
return;
}
self.selectedPayway = payway;
[self.tableView reloadData];
}
如上所示的方法全部來自于委托和協(xié)議丝里。利用委托和協(xié)議杯聚,你可以把主要精力放到邏輯業(yè)務(wù)上营密,將數(shù)據(jù)綁定和事件處理交給委托和協(xié)議去完成评汰。
二、工廠模式
女媧造人的故事
東漢《風(fēng)俗通》記錄了一則神話故事:“開天辟地主儡,未有人民惨缆,女媧搏黃土做人”丰捷,講述的內(nèi)容就是大家非常熟悉的女媧造人的故事病往。開天辟地之初骄瓣,大地上并沒有生物,只有蒼茫大地畔勤,純粹而潔凈的自然環(huán)境庆揪,寂靜而又寂寞妨托,于是女媧決定創(chuàng)造一個(gè)新物種(即人類)來增加世界的繁榮,怎么制造呢仔掸?
別忘了女媧是神仙医清,沒有辦不到的事情卖氨,造人的過程是這樣的:首先,女媧采集黃土捏成人的形狀柏腻,然后放到八卦爐中燒制五嫂,最后放置到大地上生長肯尺,工藝過程是沒有錯(cuò)的,但是意外隨時(shí)都會發(fā)生:
第一次烤泥人槐臀,感覺應(yīng)該熟了氓仲,往大地上一放得糜,哇朝抖,沒烤熟槽棍!于是一個(gè)白人誕生了L俊(這也是缺乏經(jīng)驗(yàn)的最好證明)
第二次烤泥人,上一次沒烤熟豌拙,這次多烤一會兒题暖,放到世間一看,嘿唯绍,熟過頭了枝誊,于是黑人誕生了!
第三次烤泥人绝骚,一邊燒制一邊察看压汪,直到表皮微黃古瓤,嘿,真正好穿香,于是黃色人種出現(xiàn)了叽奥!
這個(gè)造人過程是比較有意思的,是不是可以通過軟件開發(fā)來實(shí)現(xiàn)這個(gè)過程呢魔市?古人云:“三人行,必有我?guī)熝伞贝拢诿嫦驅(qū)ο蟮乃季S中将宪,萬物皆對象,是對象我們就可以通過軟件設(shè)計(jì)來實(shí)現(xiàn)印蔗。首先對造人過程進(jìn)行分析丑勤,該過程涉及三個(gè)對象:女媧、八卦爐耙厚、三種不同膚色的人岔霸。女媧可以使用場景類Client來表示,八卦爐類似于一個(gè)工廠型宝,負(fù)責(zé)制造生產(chǎn)產(chǎn)品(即人類)诡曙,三種不同膚色的人略水,他們都是同一個(gè)接口下的不同實(shí)現(xiàn)類劝萤,都是人嘛,只是膚色跨释、語言不同厌处,對于八卦爐來說都是它生產(chǎn)出的產(chǎn)品。分析完畢缆娃,我們就可以畫出如圖8-1所示的類圖。
圖8-1
類圖比較簡單暖侨,AbstractHumanFactory是一個(gè)抽象類字逗,定義了一個(gè)八卦爐具有的整體功能宅广,HumanFactory為實(shí)現(xiàn)類,完成具體的任務(wù)——?jiǎng)?chuàng)建人類挖息;Human接口是人類的總稱兽肤,其三個(gè)實(shí)現(xiàn)類分別為三類人種;NvWa類是一個(gè)場景類电禀,負(fù)責(zé)模擬這個(gè)場景笤休,執(zhí)行相關(guān)的任務(wù)。
我們定義的每個(gè)人種都有兩個(gè)方法:getColor(獲得人的皮膚顏色)和talk(交談)政基,其源代碼如代碼清單8-1所示沮明。
代碼清單8-1 人類總稱
public interface Human{
//每個(gè)人種的皮膚都有相應(yīng)的顏色
public void getColor()窍奋;
//人類會說話
public void talk();
}
接口Human是對人類的總稱江场,每個(gè)人種都至少具有兩個(gè)方法窖逗,黑色人種、黃色人種佑附、白色人種的代碼分別如代碼清單8-2、代碼清單8-3啄骇、代碼清單8-4所示瘟斜。
代碼清單8-2 黑色人種
public class BlackHuman implements Human{
public void getColor(){
System.out.println("黑色人種的皮膚顏色是黑色的!")虽惭;
}
public void talk(){
System.out.println("黑人會說話蛇尚,一般人聽不懂。")匆笤;
}
}
代碼清單8-3黃色人種
public class YellowHuman implements Human{
public void getColor(){
System.out.println("黃色人種的皮膚顏色是黃色的炮捧!")惦银;
}
public void talk(){
System.out.println("黃色人種會說話,一般說的都是雙字節(jié)书蚪。")迅栅;
}
}
代碼清單8-4 白色人種
public class WhiteHuman implements Human{
public void getColor(){
System.out.println("白色人種的皮膚顏色是白色的!")箩艺;
}
public void talk(){
System.out.println("白色人種會說話,一般都是但是單字節(jié)榨惰。");
}
}
所有的人種定義完畢居凶,下一步就是定義一個(gè)八卦爐,然后燒制人類侠碧。我們想象一下,女媧最可能給八卦爐下達(dá)什么樣的生產(chǎn)命令呢药蜻?應(yīng)該是“給我生產(chǎn)出一個(gè)黃色人種(YellowHuman類)”替饿,而不會是“給我生產(chǎn)一個(gè)會走、會跑踱卵、會說話惋砂、皮膚是黃色的人種”绳锅,因?yàn)檫@樣的命令增加了交流的成本,作為一個(gè)生產(chǎn)的管理者罗标,只要知道生產(chǎn)什么就可以了积蜻,而不需要事物的具體信息。通過分析宙拉,我們發(fā)現(xiàn)八卦爐生產(chǎn)人類的方法輸入?yún)?shù)類型應(yīng)該是Human接口的實(shí)現(xiàn)類丙笋,這也解釋了為什么類圖上的AbstractHumanFactory抽象類中createHuman方法的參數(shù)為Class類型御板。其源代碼如代碼清單8-5所示。
代碼清單8-5 抽象人類創(chuàng)建工廠
public abstract class AbstractHumanFactory{
public abstract<T extends Human>T createHuman(Class<T>c)敬鬓;
}
注意,我們在這里采用了泛型(Generic)础芍,通過定義泛型對createHuman的輸入?yún)?shù)產(chǎn)生兩層限制:
- 必須是Class類型数尿;
- 必須是Human的實(shí)現(xiàn)類。
其中的“?”表示的是诊杆,只要實(shí)現(xiàn)了Human接口的類都可以作為參數(shù)嫩实,泛型是JDK 1.5中的一個(gè)非常重要的新特性,它減少了對象間的轉(zhuǎn)換宰缤,約束其輸入?yún)?shù)類型慨灭,對Collection集合下的實(shí)現(xiàn)類都可以定義泛型球及。有關(guān)泛型的詳細(xì)知識,請參考相關(guān)的Java語法文檔筹陵。
目前女媧只有一個(gè)八卦爐镊尺,其實(shí)現(xiàn)生產(chǎn)人類的方法,如代碼清單8-6所示语稠。
代碼清單8-6 人類創(chuàng)建工廠
public class HumanFactory extends AbstractHumanFactory{
public<T extends Human>T createHuman(Class<T>c){
//定義一個(gè)生產(chǎn)的人種
Human human=null弄砍;
try{
//產(chǎn)生一個(gè)人種
human=(Human)Class.forName(c.getName()).newInstance()音婶;
}catch(Exception e){
System.out.println("人種生成錯(cuò)誤!")先口;
}
return(T)human瞳收;
}
}
人種有了,八卦爐也有了螟深,剩下的工作就是女媧采集黃土,然后命令八卦爐開始生產(chǎn)凡蜻,其過程如代碼清單8-7所示垢箕。
代碼清單8-7 女媧類
public class NvWa{
public static void main(String[]args){
//聲明陰陽八卦爐
AbstractHumanFactory YinYangLu=new HumanFactory();
//女媧第一次造人忠荞,火候不足帅掘,于是白人產(chǎn)生了
System.out.println("--造出的第一批人是白色人種--")修档;
Human whiteHuman=YinYangLu.createHuman(WhiteHuman.class);
whiteHuman.getColor()讥邻;
whiteHuman.talk()院峡;
//女媧第二次造人,火候過足鲫惶,于是黑人產(chǎn)生了
System.out.println("\n--造出的第二批人是黑色人種--")舱禽;
Human blackHuman=YinYangLu.createHuman(BlackHuman.class)字柠;
blackHuman.getColor()启妹;
blackHuman.talk()监徘;
//第三次造人六水,火候剛剛好,于是黃色人種產(chǎn)生了
System.out.println("\n--造出的第三批人是黃色人種--")睛榄;
Human yellowHuman=YinYangLu.createHuman(YellowHuman.class)想帅;
yellowHuman.getColor();
yellowHuman.talk()旨剥;
}
}
人種有了浅缸,八卦爐有了,負(fù)責(zé)生產(chǎn)的女媧也有了蚌父,激動(dòng)人心的時(shí)刻到來了梢什,我們運(yùn)行一下朝聋,結(jié)果如下所示。
--造出的第一批人是白色人種--
白色人種的皮膚顏色是白色的荔睹!
白色人種會說話言蛇,一般都是但是單字節(jié)。
--造出的第二批人是黑色人種--
黑色人種的皮膚顏色是黑色的吨拗!
黑人會說話婿斥,一般人聽不懂。
--造出的第三批人是黃色人種--
黃色人種的皮膚顏色是黃色的娇妓!
黃色人種會說話活鹰,一般說的都是雙字節(jié)只估。
哇蛔钙,人類的生產(chǎn)過程就展現(xiàn)出來了夸楣!這個(gè)世界就熱鬧起來了子漩,黑人石洗、白人、黃人都開始活動(dòng)了缕棵,這也正是我們現(xiàn)在的真實(shí)世界涉兽。以上就是工廠方法模式
工廠方法模式的優(yōu)點(diǎn)
首先,良好的封裝性别厘,代碼結(jié)構(gòu)清晰拥诡。一個(gè)對象創(chuàng)建是有條件約束的,如一個(gè)調(diào)用者需要一個(gè)具體的產(chǎn)品對象冗懦,只要知道這個(gè)產(chǎn)品的類名(或約束字符串)就可以了仇祭,不用知道創(chuàng)建對象的艱辛過程,降低模塊間的耦合没讲。
其次华弓,工廠方法模式的擴(kuò)展性非常優(yōu)秀。在增加產(chǎn)品類的情況下贰谣,只要適當(dāng)?shù)匦薷木唧w的工廠類或擴(kuò)展一個(gè)工廠類,就可以完成“擁抱變化”吱抚。例如在我們的例子中秘豹,需要增加一個(gè)棕色人種,則只需要增加一個(gè)BrownHuman類啄刹,工廠類不用任何修改就可完成系統(tǒng)擴(kuò)展凄贩。
再次,屏蔽產(chǎn)品類昵时。這一特點(diǎn)非常重要椒丧,產(chǎn)品類的實(shí)現(xiàn)如何變化,調(diào)用者都不需要關(guān)心句柠,它只需要關(guān)心產(chǎn)品的接口久橙,只要接口保持不變,系統(tǒng)中的上層模塊就不要發(fā)生變化缸榄。因?yàn)楫a(chǎn)品類的實(shí)例化工作是由工廠類負(fù)責(zé)的祝拯,一個(gè)產(chǎn)品對象具體由哪一個(gè)產(chǎn)品生成是由工廠類決定的。在數(shù)據(jù)庫開發(fā)中鹰贵,大家應(yīng)該能夠深刻體會到工廠方法模式的好處:如果使用JDBC連接數(shù)據(jù)庫碉输,數(shù)據(jù)庫從MySQL切換到Oracle亭珍,需要改動(dòng)的地方就是切換一下驅(qū)動(dòng)名稱(前提條件是SQL語句是標(biāo)準(zhǔn)語句)枝哄,其他的都不需要修改阻荒,這是工廠方法模式靈活性的一個(gè)直接案例。