Immutable Model
我們以UserModle為例欣范,我們可以像這樣創(chuàng)建:
public class UserModel: NSObject {
public var userId: NSNumber
public var name: String?
public var email: String?
public var age: Int?
public var address: String?
init(userId: NSNumber) {
self.userId = userId
super.init()
}
}
用的時候可以像這樣:
let userModel = UserModel(userId: 1)
user.email = "335050309@qq.com"
user.name = "roy"
user.age = 27
user.address = "上海市楊浦區(qū)"
這樣創(chuàng)建一個User對象好處是彈性很大变泄,我可以隨意選擇設(shè)定某個property的值,但是背后同樣帶有很大的缺點恼琼,就是這個Model變得異常開放妨蛹,不安分,這種Model我們一般叫Mutable Model晴竞。有的時候我們需要Mutable Model蛙卤,但大部分的時候出于數(shù)據(jù)安全和解耦考慮我們不希望創(chuàng)建的property在外部可以隨意改變,在初始化后不可變的Model叫做Immutable Model,在開發(fā)中我的建議盡量使用Immutable Model颤难。我們通過把property設(shè)置成readonly神年,在Swift可以用let或者private(set)。也就是這樣:
public class UserModel: NSObject {
public let userId: NSNumber
public private(set) var name: String?
public private(set) var email: String?
public private(set) var age: Int?
public private(set) var address: String?
}
那么怎么寫初始化方法呢行嗤?
Initializer mapping arguments to properties
當(dāng)我們把property設(shè)置成readonly后已日,我們只能在init的時候賦值,這個時候就變成這樣:
public class User: NSObject {
public var userId: NSNumber
public var name: String?
public var email: String?
public var age: Int?
public var address: String?
init(userId: NSNumber, name: String?, email: String, age: Int, address: String) {
self.userId = userId
super.init()
self.name = name
self.email = email
self.age = age
self.address = address
}
}
使用的時候就變成這樣:
let user = User.init(userId: 1, name: "335050309@qq.com", email: "roy", age: 27, address: "上海市楊浦區(qū)")
這樣創(chuàng)建Model安全可靠飘千,大多數(shù)時候是有效的栈雳,但是也有一些缺點:
- 如果property很多,init方法就有很多形參逆济,然后變得又臭又長奖慌。
- 有的時候我們只需要Model的某些property松靡,這樣我們可能為各個不同的需求寫不同的init方法,最終讓UserModel變得很龐大岛马。
Initializer taking dictionary
初始化的時候注入一個字典啦逆,就是下面的樣子:
public class UserModel: NSObject {
public let userId: NSNumber
public private(set) var name: String?
public private(set) var email: String?
public private(set) var age: Int?
public private(set) var address: String?
init(dic: NSDictionary) {
self.userId = (dic["userId"] as? NSNumber)!
super.init()
self.name = dic["name"] as? String
self.email = dic["email"] as? String
self.age = dic["age"] as? Int
self.address = dic["address"] as? String
}
}
很顯然這解決上一種第一個缺點夏志,但是還是有一個不足之處:
- 如果字典沒有某個屬性對應(yīng)的key的時候會崩潰苛让,編譯器并不能幫助我們排查這種運行時的崩潰。
- 不能很好的滿足某些時候只需要Model的某些property的需求瘦材。
Mutable subclass
我們看看Improving Immutable Object Initialization in Objective-C關(guān)于這個是怎么描述的
We end up unsatisfied and continue our quest for the best way to initialize immutable objects. Cocoa is a vast land, so we can – and should – steal some of the ideas used by Apple in its frameworks. We can create a mutable subclass of Reminder class which redefines all properties as readwrite:
@interface MutableReminder : Reminder <NSCopying, NSMutableCopying>
@property (nonatomic, copy, readwrite) NSString *title;
@property (nonatomic, strong, readwrite) NSDate *date;
@property (nonatomic, assign, readwrite) BOOL showsAlert;
@end
Apple uses this approach for example in NSParagraphStyle and NSMutableParagraphStyle. We move between mutable and immutable counterparts with -copy and -mutableCopy. The most common case matches our example: a base class is immutable and its subclass is mutable.
The main disadvantage of this way is that we end up with twice as many classes. What's more, mutable subclasses often exist only as a way to initialize and modify their immutable versions. Many bugs can be caused by using a mutable subclass by accident. For example, a mental burden shows in setting up properties. We have to always check if a mutable subclass exists, and if so use copy modifier instead of strong for the base class.
大致意思是創(chuàng)建一個可變子類食棕,它將所有屬性重新定義為readwrite宣蠕。這種方式的主要缺點是我們最終得到兩倍的類。而且镀层,可變子類通常僅作為初始化和修改其不可變版本的方式存在唱逢。偶然使用可變子類可能會導(dǎo)致許多錯誤屋休。例如,在設(shè)置屬性時會出現(xiàn)心理負(fù)擔(dān)痪枫。我們必須始終檢查是否存在可變子類奶陈。
還有一點這種方式只能在Objective-C中使用附较。
Builder pattern
Builder pattern 模式需要我們使用一個Builder來創(chuàng)建目標(biāo)對象,目標(biāo)對象的property依舊是readonly徐勃,但是Builder的對應(yīng)property卻可以選擇為readwrite僻肖。依舊用UserModel為例扎酷,我們需要為其進(jìn)行適當(dāng)?shù)母脑於舸遥脑熘螅?/p>
typealias UserModelBuilderBlock = (UserModelBuilder) -> UserModelBuilder
public class UserModel: NSObject{
public let userId: NSNumber
public private(set) var name: String?
public private(set) var email: String?
public private(set) var age: Int?
public private(set) var address: String?
init(userId: NSNumber) {
self.userId = userId
super.init()
}
convenience init(userId: NSNumber ,with block: UserModelBuilderBlock){
let userModelBuilder = block(UserModelBuilder.init(userId: userId))
self.init(userId: userModelBuilder.userId)
self.email = userModelBuilder.email
self.name = userModelBuilder.name
self.age = userModelBuilder.age
self.address = userModelBuilder.address
}
}
之后是對應(yīng)的Builder
class UserModelBuilder: NSObject {
public let userId: NSNumber
public var name: String?
public var email: String?
public var age: Int?
public var address: String?
init(userId: NSNumber) {
self.userId = userId
super.init()
}
}
然后可以像下面這樣使用:
let userModle = UserModel(userId: 1) { (builder) -> UserModelBuilder in
builder.email = "335050309@qq.com"
builder.name = "roy"
builder.age = 27
builder.address = "上海市楊浦區(qū)"
return builder
}
這種方式雖然我們需要為Model再創(chuàng)建一個Builder凡纳,略顯啰嗦和復(fù)雜荐糜,但是當(dāng)property較多,對Model的需求又比較復(fù)雜的時候這又確實是一種值得推薦的方式延塑。
以上全是Swift的代碼實現(xiàn)答渔,下面我再貼上對應(yīng)的OC代碼
#import <Foundation/Foundation.h>
@interface RUserModelBuilder : NSObject
@property (nonatomic, strong, readwrite, nonnull) NSNumber *userId;
@property (nonatomic, copy, readwrite, nullable) NSString *name;
@property (nonatomic, copy, readwrite, nullable) NSString *email;
@property (nonatomic, copy, readwrite, nullable) NSNumber *age;
@property (nonatomic, copy, readwrite, nullable) NSString *address;
@end
typedef RUserModelBuilder *__nonnull(^RUserModelBuilderBlock)(RUserModelBuilder *__nonnull userModelBuilder);
@interface RUserModel : NSObject
@property (nonatomic, strong, readonly, nonnull) NSNumber *userId;
@property (nonatomic, copy, readonly, nullable) NSString *name;
@property (nonatomic, copy, readonly, nullable) NSString *email;
@property (nonatomic, copy, readonly, nullable) NSNumber *age;
@property (nonatomic, copy, readonly, nullable) NSString *address;
+ (nonnull instancetype)buildWithBlock:(nonnull RUserModelBuilderBlock)builderBlock;
@end
#import "RUserModel.h"
@implementation RUserModelBuilder
@end
@interface RUserModel ()
@property (nonatomic, strong, readwrite, nonnull) NSNumber *userId;
@property (nonatomic, copy, readwrite, nullable) NSString *name;
@property (nonatomic, copy, readwrite, nullable) NSString *email;
@property (nonatomic, copy, readwrite, nullable) NSNumber *age;
@property (nonatomic, copy, readwrite, nullable) NSString *address;
@end
@implementation RUserModel
#pragma mark - NSCopying
+ (nonnull instancetype)buildWithBlock:(nonnull RUserModelBuilderBlock)builderBlock {
RUserModelBuilder *userModelBuilder = builderBlock([[RUserModelBuilder alloc] init]);
RUserModel *userModel = [[RUserModel alloc] init];
userModel.userId = userModelBuilder.userId;
userModel.name = userModelBuilder.name;
userModel.email = userModelBuilder.email;
userModel.age = userModelBuilder.age;
userModel.address = userModelBuilder.address;
return userModel;
}
@end
demo地址ImmutableModel
參考文章:
Improving Immutable Object Initialization in Objective-C
iOS 創(chuàng)建對象的姿勢