iOS 建立對象的姿勢

來源:互聯網
上載者:User

標籤:

在寫 ios開發中,怎麼樣去 new 一個新對象出來,都有一些講究在裡面。使用不同的姿勢去建立對象,對後期維護所造成的影響會存在細微的差別。

init 建立

在之前一篇分析 iOS 代碼耦合的文章中,提到過當我們給一個對象的 property 賦值的時候,通過 init方法傳入參數來初始化 property 會讓我們的代碼更可靠。

有些人在定義帶 property 的 class 的時候,會這樣定義:

@interface User : [email protected] (nonatomic, strong) NSNumber* userID;@end

使用的時候如下:

Useruser = [[User alloc] init];user.userID = @1000;

尤其是在定義 model 的時候,很容易寫出這種,先 init,而後挨個給 property 賦值的代碼。這種代碼的問題在於 property 對於外部是可寫的,property 處於隨時可能變化的狀態。之前不少篇文章中都強調過immutable 的重要性,同樣對於一個 class,我們也應該優先考慮設計成 immutable 的。

initWith 建立

如果將 property 都設定成 readonly 的,或者不暴露 property,property 的賦值都通過 initWith 的方式來初始化,就可以得到一個具備 immutable 的 class 定義了,具體到上面的例子代碼如下:

//User.h@interface User : [email protected] (nonatomicstrongreadonly) NSNumber* userID;

- (instancetype)initWithUserID:(NSNumber*)uid;@end

//User.m@implementation User

- (instancetype)initWithUserID:(NSNumber*)uid {

self = [super init];

if (!self) {

return nil;

}

_userID = uid;

return self;

}@end

userID 在 .h 檔案當中是 readonly 的,userID 只有一次被賦值的機會,即在 User 的 initWith 方法中。這種方式的好處是一旦 User 對象建立完畢之後,就處於 immutable 的狀態,property 都是不可修改的,安全可靠。

Designated initializer

Apple 為了方便開發人員使用 init 方法,引入了一種名為 designated initializer 的 pattern。主要用來管理當一個 class 擁有多個 property 需要賦值的情境。比如上面我們的 User 類:

@interface User : [email protected] (nonatomic, strong, readonly) NSNumber* userID;@property (nonatomic, strong, readonly) NSString* userName;@property (nonatomic, strong, readonly) NSString* signature;@end

有些情境需要初始化 userID 和 userName,而有些情境只需要初始化 userID 和 signature,所以我們需要提供多個 initWith 方法給不同的情境使用。為了管理 initWith 方法,Apple 將 init 方法分為兩種類型:designated initializer 和 convenience initializer (又叫 secondary initializer) 。

designated initializer 只有一個,它會為 class 當中每個 property 都提供一個初始值,是最完整的initWith 方法。convenience initializer 則可以有很多個,它可以選擇只初始化部分的property。convenience initializer 最後到會調用到 designated initializer,所以 designated initializer 也可以叫做 final initializer。

無論我們定義何種類型的 class,給 class 中的每個 property 都賦予一個初始值是個很好的習慣,可以避免掉一些意外的 bug 產生,這也是 designated initializer 的重要職責。

在實際的項目當中,一個 class 的 property 數目可能會隨著業務的增長而增加,最後的結果就是會產生越來越多的 convenience initializer。上述的 User 類,如果是 3 個 property,極端的情況下最多可以有 7個 init 方法。Peak君在閱讀代碼的時候,也確實看到過有些 class 定義了一連串整整齊齊擺放的 init 方法,代碼雖然看著規範,但顯得囉嗦,而且每次需要肉眼搜尋適合的 init 方法。

其實我們還可以用另一種姿勢來 init 我們的對象。

Builder pattern

最初是在學習 Android 的時候,發現這個 builder pattern 也可以用來構建對象,而且可以很好的解決init 方法過多難以管理的問題。先來看下如何?,顧名思義,builder pattern 使用另一個名為 builder 的類來建立我們的目標對象,還是上面的例子,代碼如下:

//UserBuilder.h@interface UserBuilder : [email protected] (nonatomicstrongreadonly) NSNumber* userID;@property (nonatomicstrongreadonly) NSString* userName;@property (nonatomicstrongreadonly) NSString* signature;

- (UserBuilder*)userID:(NSNumber*)userID;

- (UserBuilder*)userName:(NSString*)userName;

- (UserBuilder*)signature:(NSString*)signature;@end

//UserBuilder.m@implementation UserBuilder

- (UserBuilder*)userID:(NSNumber*)userID {

_userID = userID;

return self;

}

- (UserBuilder*)userName:(NSString*)userName {

_userName = userName;

return self;

}

- (UserBuilder*)signature:(NSString*)signature {

_signature = signature;

return self;

}@end

接下來 User 的 init 方法從 Builder 中擷取 property 的初始值:

//User.h@interface User : [email protected] (nonatomicstrongreadonly) NSNumber* userID;@property (nonatomicstrongreadonly) NSString* userName;@property (nonatomic,strongreadonly) NSString* signature;

- (instancetype)initWithUserBuilder:(UserBuilder*)builder;@end

//User.m@implementation User

- (instancetype)initWithUserBuilder:(UserBuilder*)builder {

self = [super init];

if (!self) {

return nil;

}

_userID = builder.userID;

_userName = builder.userName;

_signature = builder.signature;

return self;

}@end

如果要建立 User 對象,則按照這種方式:

UserBuilder* builder = [[[[UserBuilder new] userName:@"peak"] userID:@1000] signature:@"roll"];

User* user = [[User alloc] initWithUserBuilder:builder];

這樣我們避免了書寫多個 init 方法,同樣 User 對象也是 immutable 的,也做到了只在 init 方法中做一次賦值操作,每個情境都可以按照自己的需求初始化部分 property,當然最後我們需要在initWithUserBuilder 中為每一個 property 賦值, initWithUserBuilder 扮演的角色類似於 designated initializer。

追求代碼美感的同學可能發現了, UserBuilder 的建立文法很醜陋,多個 [ ] 套嵌使用。為了讓代碼更好看一些,我們也可以使用 block 來建立:

Useruser = [User userWithBlock:^(UserBuilder* builder) {

builder.userName = @"peak";

builder.userID = @1000;

builder.signature = YES;

}];

builder pattern 在 Android 平台使用的比較多,我在 iOS 平台上鮮少有看到使用的情境。builder pattern 的不足之處也比較明顯,需要另外定義一個 builder 類,多寫一些代碼(property 基本都重複寫了一遍)。個人覺得,在 property 數量較多,初始化的情境也比較多的時候,在 iOS 上使用 builder pattern也會是個不錯的方案。

designated initializer vs builder pattern,這二者之間的不同其實很好的體現了語言本身的差異性。學習過 java 的同學就能明白,在 java 的世界中,一切都是可以被封裝成對象的,使用 java 的時候,經常要定義各式各樣的輔助類來完成某個任務,好處是封裝度高,類職責劃分粒度小,缺點是類太多,有時候會為了封裝而封裝,某些情境代碼反而不夠直觀。

總結

簡單梳理了下建立對象的不同姿勢,希望對大家有些協助。

 

來源:MrPeak技術分享

iOS 建立對象的姿勢

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.