OC中的類簇模式:
OC中的類簇模式和工廠設(shè)計(jì)模式的思想是一致的是嗜。就是若有幾個(gè)功能接近的類溢谤,我們可以給它們定義一個(gè)公共的“抽象類”媳危。使用者只需要和這個(gè)公用的抽象類來打交道就行了,而不必關(guān)心具體某類的細(xì)節(jié)患整。
“工廠模式”這個(gè)名字就非常形象:我們可能會(huì)使用“刀”拜效,“斧”,“錘子”等各種工具各谚,人類一開始都是自產(chǎn)自用的紧憾,也就是說自己造刀斧等工具,但是這樣的話使用者得了解昌渤、掌握各種工具的生產(chǎn)方法或者使用方式赴穗,這對使用者而言是及其麻煩的。社會(huì)漸漸發(fā)展膀息,開始出現(xiàn)了專門制造這些個(gè)工具的“工廠”般眉,只需要告訴它我們需要什么樣的工具,工廠就會(huì)給我們什么樣的工具潜支。比如我想要把錘子甸赃,只需要告訴工廠,我需要的工具是一把錘子冗酿,那工廠內(nèi)部會(huì)開動(dòng)它們的“錘子”生產(chǎn)部門來生產(chǎn)出錘子埠对。
** 看個(gè)代碼例子: **
Staff類就是個(gè)工廠類,它是“抽象基類”裁替,我們需要返回什么樣的員工项玛,只需要傳入員工類型type參數(shù)就行了,具體是怎么生成的弱判,我們不必耗費(fèi)精力去關(guān)心襟沮,Staff這個(gè)工廠類內(nèi)部會(huì)去實(shí)現(xiàn),我們只需等著結(jié)果就行了。
#import "ViewController.h"
#import "Staff.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
Staff *staff = [Staff staffWithType:StaffType_Coder];
[staff doSomework];
NSLog(@"----%@----",[staff class]);
}
@end
// Staff.h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, StaffType)
{
StaffType_Coder,
StaffType_Designer,
};
@interface Staff : NSObject
// 傳入type參數(shù)开伏,生成不同類型的員工
+ (Staff *)staffWithType:(StaffType)type;
// 做一些工作...
- (void)doSomework;
@end
// Staff.m
#import "Staff.h"
#import "CoderStaff.h"
#import "DesignerStaff.h"
@implementation Staff
// 傳入type參數(shù)膀跌,生成不同類型的員工
+ (Staff *)staffWithType:(StaffType)type
{
switch (type)
{
case StaffType_Coder:
return [CoderStaff new];
break;
case StaffType_Designer:
return [DesignerStaff new];
break;
default:
break;
}
}
// 做一些工作...
- (void)doSomework
{
NSLog(@"the staff is working...");
}
// 為了避免調(diào)用者直接調(diào)用基類的初始化方法,可以返回nil,或者拋出異常提醒調(diào)用者硅则。
- (instancetype)init
{
return nil;
}
@end
所謂設(shè)計(jì)模式淹父,就是在長期實(shí)踐過程中琢磨出的更好的一種方式,不是說非使用設(shè)計(jì)模式不可怎虫,非用設(shè)計(jì)模式其他方式不可實(shí)現(xiàn)。其實(shí)像上面的例子困介,我們完全可以把CoderStaff大审、DesignerStaff這些類提供給調(diào)用者,在其中分別實(shí)現(xiàn)相應(yīng)的初始化方法座哩。但是這樣的話徒扶,調(diào)用者就得了解每個(gè)類提供的方法等,而“類簇”妙就妙在讓調(diào)用者不必勞心關(guān)注太多細(xì)節(jié)根穷,需要什么告訴我姜骡,我給你。
關(guān)于初始化方法
我們通常在自己寫的獨(dú)立的控件等時(shí)需要給外部提供調(diào)用接口屿良,當(dāng)然首要的是初始化方法圈澈,而且通常要提供多個(gè)初始化方法。
其中尘惧,我們應(yīng)定義一個(gè)“全能初始化方法”康栈,即該初始化方法能為對象提供足夠的信息以完成創(chuàng)建。而其他初始化方法在內(nèi)部其實(shí)都調(diào)用它喷橙。
// Rectangle.h
#import <Foundation/Foundation.h>
@interface Rectangle : NSObject
@property (nonatomic, assign) float width;
@property (nonatomic, assign) float height;
- (instancetype)initWithWidth:(float)width andHeight:(float)height;
@end
// Rectangle.m
#import "Rectangle.h"
@implementation Rectangle
- (instancetype)init
{
return [self initWithWidth:100.f andHeight:20.f];
}
- (instancetype)initWithWidth:(float)width andHeight:(float)height
{
self = [super init];
if(self)
{
_width = width;
_height = height;
}
return self;
}
@end
Rectangle類提供了initWithWidth:andHeight:
這個(gè)初始化方法(它算該類的全能初始化方法)啥么。但是不排除調(diào)用者調(diào)用了init
這個(gè)模式初始化方法,所以我們重寫它贰逾,在其內(nèi)部調(diào)用全能初始化方法悬荣,傳入默認(rèn)值的參數(shù)。
假如疙剑,此時(shí)又定義了一個(gè)子類Square氯迂。它繼承于基類Rectangle。
// Square.h
#import "Rectangle.h"
@interface Square : Rectangle
- (instancetype)initWithLength:(float)length;
@end
Square.h暴露給外部一個(gè)initWithLength:
的初始化方法核芽。
// Square.m
#import "Square.h"
@implementation Square
- (instancetype)init
{
self = [super init];
return [self initWithLength:100.f];
}
// 重寫父類的初始化方法
- (instancetype)initWithWidth:(float)width andHeight:(float)height
{
float resultLength = MAX(width, height);
return [self initWithLength:resultLength];
}
- (instancetype)initWithLength:(float)length
{
return [self initWithWidth:length andHeight:length];
}
@end
需要注意的是囚戚,既然Square繼承于基類Rectangle,那調(diào)用者也有調(diào)用基類的initWithWidth:andHeight:
這個(gè)初始化方法的可能性轧简。要么驰坊,我們可以像上面一樣,返回一個(gè)以width和height倆較大值為邊長的正方形哮独。但也可從另外一個(gè)角度想:認(rèn)為這屬于調(diào)用者調(diào)用錯(cuò)誤拳芙,而拋出異常提醒調(diào)用者不該使用父類的初始化方法察藐。
盡量使用不可變對象。
“盡量使用不可變對象”旨在安全考量舟扎。
** 有時(shí)我們只想在類內(nèi)部修改數(shù)據(jù)分飞,但不想令這些數(shù)據(jù)為外人所改動(dòng)這時(shí),我們可以把暴露給外部的屬性聲明為readonly睹限,但在內(nèi)部重新聲明為readwrite譬猫。**
// Staff.m
#import <Foundation/Foundation.h>
@interface Staff : NSObject
@property (nonatomic, copy, readonly)NSString *staffId; // 工號
@property (nonatomic, copy, readonly)NSString *salay; // 薪水
@end
// Staff.m
#import "Staff.h"
#import "CoderStaff.h"
#import "DesignerStaff.h"
@interface Staff ()
@property (nonatomic, copy, readwrite)NSString *staffId;
@property (nonatomic, copy, readwrite)NSString *salay;
@end
@implementation Staff
@end
在Staff.m的Extension(拓展)中重新聲明屬性為readwirite。現(xiàn)在羡疗,只能在Staff內(nèi)部設(shè)置這些屬性值染服,外部僅是可讀的。
** 注意 :** 即便上面這樣寫了叨恨,但是還是有法子在外部修改屬性值的柳刮,比如可以使用KVC修改屬性值。不過痒钝,沒有絕對的安全秉颗,上面這樣寫總歸要規(guī)范嚴(yán)謹(jǐn)?shù)枚唷?/p>
- (void)viewDidLoad
{
[super viewDidLoad];
Staff *staff = [[Staff alloc] init];
[staff setValue:@"10000.00" forKey:@"salary"];
NSLog(@"salary----%@",staff.salary);
}
// 2016-03-11 00:39:34.791 WangDemo310[5844:106406] salary----10000.00
** 不要把可變的集合類作為屬性公開,而應(yīng)該提供相關(guān)方法送矩,以此來修改對象中可變的集合類 **
比如下面Person類蚕甥,它是可以進(jìn)行添加、刪除好友操作的益愈,但是作為屬性暴露給外部的是一個(gè)不可變的(只可讀的)的NSArray梢灭,而在內(nèi)部卻是
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy)NSString *personId;
@property (nonatomic, copy)NSString *name;
@property (nonatomic, strong)NSArray *myFriendsArr;
- (instancetype)initWithPerson:(NSString *)personId andName:(NSString *)name;
- (void)addFriend:(Person *)perspon;
- (void)removeFriend:(Person *)person;
@end
// Person.m
#import "Person.h"
@interface Person ()
{
NSMutableArray *_friendsMarr; // 內(nèi)部維護(hù)的其實(shí)是個(gè)可變的數(shù)組
}
@end
@implementation Person
- (instancetype)initWithPerson:(NSString *)personId andName:(NSString *)name
{
self = [super init];
if(self)
{
_personId = personId;
_name = name;
_friendsMarr = [[NSMutableArray alloc] init];
}
return self;
}
- (NSArray *)myFriendsArr
{
return [_friendsMarr copy]; // _friendsMarr本是可變的,進(jìn)行copy操作蒸其,成為不可變的敏释,然后返回給外部。
}
- (void)addFriend:(Person *)perspon
{
[_friendsMarr addObject:perspon];
}
- (void)removeFriend:(Person *)person
{
[_friendsMarr removeObject:person];
}
@end
在Peson內(nèi)部維護(hù)的其實(shí)是個(gè)可變的NSMutableArray摸袁,可以進(jìn)行add和remove操作钥顽,但通過copy動(dòng)作后暴露給外部卻是一個(gè)不可變的NSArray。這和第一個(gè)例子的思想其實(shí)是一致的:** 只給外部暴露需要暴露的靠汁,而且暴露的東西得不可變蜂大,外人不能更改。 **
關(guān)于OC中的錯(cuò)誤蝶怔、異常
首先奶浦,OC中的異常應(yīng)該盡量少用,因?yàn)椤白詣?dòng)引用計(jì)數(shù)”不是異常安全的踢星。即當(dāng)拋出異常時(shí)澳叉,本應(yīng)在作用域末尾進(jìn)行釋放的代碼便不會(huì)執(zhí)行了,這就會(huì)造成內(nèi)存泄露。
所以成洗,異常只應(yīng)該用于極其嚴(yán)重的錯(cuò)誤五督,比如本應(yīng)使用子類方法,調(diào)用者卻使用了“抽象類”基類的方法瓶殃,這時(shí)應(yīng)該重寫基類的這些方法充包,在其中拋出異常,提醒調(diào)用者不能調(diào)用基類的方法遥椿。
對于一般性錯(cuò)誤基矮,可以返回nil和或者0什么的提示調(diào)用者這樣做有問題。
而NSError修壕,可以把導(dǎo)致錯(cuò)誤的信息回饋給調(diào)用者愈捅。
NSError常見用法:
1.在代理方法或者block回調(diào)中把錯(cuò)誤信息傳遞給調(diào)用者;
2.經(jīng)由方法的“輸出參數(shù)”返回給調(diào)用者慈鸠,(NSError **)指向指針的指針。
- (BOOL)doSomething:(NSError **)error;
傳遞給方法的參數(shù)是個(gè)指針灌具,該指針又指向另外一個(gè)指向NSError對象的指針青团。或者也可以把它當(dāng)成一個(gè)直接指向NSError對象的指針咖楣。
** 該方法一般返回BOOL類型的結(jié)果督笆,表示操作成功與否。這么一來诱贿,該方法既可以返回表示該操作成功與否的bool值娃肿,而且還能經(jīng)由“輸出參數(shù)”把NSError對象回傳給調(diào)用者,告訴調(diào)用者該操作返回NO的錯(cuò)誤細(xì)節(jié)珠十。**
// 調(diào)用
NSError *error = nil;
BOOL result = [self doSomething:&error];
if(error){
// 說明有錯(cuò)誤料扰,此時(shí)result一般為NO
NSLog(@"錯(cuò)誤細(xì)節(jié):%@",error.domain);
}
- (BOOL)doSomething:(NSError **)error
{
if(/* there was a error*/1) // 當(dāng)出現(xiàn)錯(cuò)誤時(shí)
{
if(error){ // 并且存在error參數(shù)
*error = [NSError errorWithDomain:@"xxx" code:-1 userInfo:nil]; // 生成error對象
return NO;
}else{
return YES;
}
}
}