圖片來(lái)自Pixabay
模型層(Model)的兩種設(shè)計(jì)方案:
- 使用自定義的初始化器横侦;
- 使用生成器模式挥萌;
一绰姻、自定義初始化器
這是設(shè)計(jì)模型層的常用方式。
// ---------------------------------------------------
// HQLUser.h
// ---------------------------------------------------
#import <Foundation/Foundation.h>
@interface HQLUser : NSObject
// 屬性的設(shè)置:向外層暴露的屬性盡量設(shè)置為不可變(immutable)引瀑、只讀(readonly)
@property (nonatomic, copy, readonly) NSString *userID;
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, copy, readonly) NSString *gender;
@property (nonatomic, copy, readonly) NSDate *dateOfBirth;
@property (nonatomic, copy, readonly) NSArray *albums;
// 指定初始化方法
- (instancetype)initWithUserID:(NSString *)userID
firstName:(NSString *)firstName
lastName:(NSString *)lastName
gender:(NSString *)gender
dateOfBirth:(NSDate *)dateOfBirth
albums:(NSArray *)albums;
@end
// ---------------------------------------------------
// HQLUser.m
// ---------------------------------------------------
#import "HQLUser.h"
@interface HQLUser ()
// 在內(nèi)部將屬性重新封裝為 readwrite狂芋,這樣屬性在內(nèi)部就是可讀可寫的。
@property (nonatomic, copy, readwrite) NSString *userID;
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@property (nonatomic, copy, readwrite) NSString *gender;
@property (nonatomic, copy, readwrite) NSDate *dateOfBirth;
@property (nonatomic, copy, readwrite) NSArray *albums;
@end
@implementation HQLUser
- (instancetype)initWithUserID:(NSString *)userID
firstName:(NSString *)firstName
lastName:(NSString *)lastName
gender:(NSString *)gender
dateOfBirth:(NSDate *)dateOfBirth
albums:(NSArray *)albums {
if (self = [super init]) {
_userID = [userID copy];
_firstName = [firstName copy];
_lastName = [lastName copy];
_gender = [gender copy];
_dateOfBirth = dateOfBirth;
_albums = [albums copy];
}
return self;
}
- (instancetype)init {
@throw [NSException exceptionWithName:@"Method Undefined"
reason:@"Use Designated Initializer Method"
userInfo:nil];
return nil;
}
@end
調(diào)用如下:
// 使用自定義的初始化器
HQLUser *user = [[HQLUser alloc] initWithUserID:@"1214138" firstName:@"Hello" lastName:@"Kitty" gender:@"man" dateOfBirth:[NSDate date] albums:[NSArray array]];
??
指定初始化方法和非指定初始化方法可以用宏來(lái)進(jìn)行標(biāo)記:
- NS_DESIGNATED_INITIALIZER
- NS_UNAVAILABLE
這種設(shè)計(jì)模式的缺點(diǎn):
- 如果類包含的屬性很多憨栽,指定初始化方法的方法名會(huì)很長(zhǎng)帜矾、非指定初始化方法的個(gè)數(shù)將會(huì)很多;
- 向下兼容問(wèn)題:如果新增一個(gè)屬性徒像,就需要重構(gòu)指定初始化方法,因此蛙讥,新版模型無(wú)法實(shí)現(xiàn)向下兼容锯蛀;
二、生成器模式
需要引入外部類進(jìn)行管理次慢。生成器有setter方法旁涤,也需要提供與模型數(shù)據(jù)完全一致的存儲(chǔ)。生成器最終也會(huì)使用初始化器迫像。
// ---------------------------------------------------
// HPUser.h
// ---------------------------------------------------
#import <Foundation/Foundation.h>
@class HPUserBuilder;
@class HPUser;
typedef void(^HPUserBuilderBlock)(HPUserBuilder *builder);
@interface HPUserBuilder : NSObject
@property (nonatomic, copy) NSString *username;
@property (nonatomic, copy) NSString *gender;
@property (nonatomic, copy) NSDate *dateOfBirth;
- (HPUser *)build;
- (HPUserBuilder *)username:(NSString *)username;
- (HPUserBuilder *)gender:(NSString *)gender;
- (HPUserBuilder *)dateOfBirth:(NSDate *)dateOfBirth;
@end
@interface HPUser : NSObject
@property (nonatomic, copy) NSString *username;
@property (nonatomic, copy) NSString *gender;
@property (nonatomic, copy) NSDate *dateOfBirth;
- (instancetype)initWithBuilder:(HPUserBuilder *)builder;
- (instancetype)initWithBlock:(HPUserBuilderBlock)block;
@end
// ---------------------------------------------------
// HPUser.m
// ---------------------------------------------------
#import "HPUser.h"
@implementation HPUserBuilder
- (HPUser *)build {
// 構(gòu)造器的初始化方法劈愚,調(diào)用的還是模型層的指定初始化方法
return [[HPUser alloc] initWithBuilder:self];
}
- (HPUserBuilder *)username:(NSString *)username {
_username = [username copy];
return self;
}
- (HPUserBuilder *)gender:(NSString *)gender {
_gender = [gender copy];
return self;
}
- (HPUserBuilder *)dateOfBirth:(NSDate *)dateOfBirth {
_dateOfBirth = dateOfBirth;
return self;
}
@end
@implementation HPUser
// 指定初始化方法
- (instancetype)initWithBuilder:(HPUserBuilder *)builder {
if (self = [super init]) {
_username = builder.username;
_gender = builder.gender;
_dateOfBirth = builder.dateOfBirth;
}
return self;
}
- (instancetype)initWithBlock:(HPUserBuilderBlock)block {
HPUserBuilder *builder = [[HPUserBuilder alloc] init];
block(builder);
// 返回的是構(gòu)造器的初始化方法
return [builder build];
}
@end
生產(chǎn)器模式解決了自定義初始化方法的兩個(gè)缺點(diǎn),它的方法名不會(huì)很長(zhǎng)闻妓、也解決了向下的兼容性問(wèn)題菌羽,調(diào)用如下:
// 1
HPUserBuilder *builder = [[[[[HPUserBuilder alloc] init] username:@"username"] gender:@"man"] dateOfBirth:[NSDate date]];
HPUser *usr = [[HPUser alloc] initWithBuilder:builder];
// 2
HPUser *user = [[HPUser alloc] initWithBlock:^(HPUserBuilder *builder) {
builder.username = @"username";
builder.gender = @"man";
builder.dateOfBirth = [NSDate date];
}];
生成器模式 其實(shí)就是在普通的 Model 層之上又封裝了一個(gè)中間層,這個(gè)中間類的屬性和 Model 層是一樣的由缆。本質(zhì)上到最后還是要去調(diào)用 Model 層的指定初始化方法注祖。
使用指南:
- 當(dāng)你的 Model 層中的屬性很多、或者后期很有可能新增其他屬性均唉,那么使用生成器模式可以解決向后的兼容性問(wèn)題(Model 層新增屬性后是晨,使用了該 Model 類的其他地方不需要因?yàn)?Model 類指定初始化方法的改變而跟著修改)。
- 當(dāng)你的 Model 層中的屬性不是很多舔箭,后期也不大可能新增屬性時(shí)罩缴,不太適合使用生成器模式,這種模式幾乎要寫原本 2 倍的代碼量层扶,有種為了封裝而封裝的感覺箫章,會(huì)延長(zhǎng)我們的開發(fā)周期,降低開發(fā)效率镜会,提高開發(fā)成本炉抒。
總結(jié)
生成器模式可以引用一句經(jīng)典的話來(lái)總結(jié):
All problems in computer science can be solved by another level of indirection.——Butler Lampson
“計(jì)算機(jī)科學(xué)領(lǐng)域的任何問(wèn)題都可以通過(guò)增加一個(gè)間接的中間層來(lái)解決”
參考
- Gaurav Vaish. 高性能iOS應(yīng)用開發(fā) [M]. 北京:人民郵電出版社,2017. 102-105
- Improving Immutable Object Initialization in Objective-C
- iOS 創(chuàng)建對(duì)象的姿勢(shì) @MrPeak