背景
日常開發(fā)中一定會遇到這種場景,在某個類中提供了這樣一個方法:
@interface TTDoSomething:NSObject
- (void)doSomethingWithName:(NSString *)name configA:(ConfigA)configA configB:(ConfigB)configB configC:(ConfigC)configC;
@end
這個方法通過四個參數(shù)name, configA, configB, configB提供了 doSomeThing的功能,可是這個方法的參數(shù)實在是有點多,有的時候我只需要一個默認(rèn) config 就好了,并不是每個 config 都需要傳入一個值,這時候可以把這個多參數(shù)的方法作為全能方法,然后以它為基礎(chǔ)實現(xiàn)幾個便利方法:
@interface TTDoSomething:NSObject
- (void)doSomethingWithName:(NSString *)name configA:(ConfigA)configA;
- (void)doSomethingWithName:(NSString *)name configB:(ConfigB)configB;
- (void)doSomethingWithName:(NSString *)name configC:(ConfigC)configC;
- (void)doSomethingWithName:(NSString *)name configA:(ConfigA)configA configB:(ConfigB)configB configC:(ConfigC)configC;
@end
@implementation TTDoSomething
(void)doSomethingWithName:(NSString *)name configA:(ConfigA)configA {
[self doSomethingWithName:name configA:configA configB:nil configC:nil];
}(void)doSomethingWithName:(NSString *)name configB:(ConfigB)configB {
[self doSomethingWithName:name configA:nil configB:configB configC:nil];
}
...
@end
但是這么做的缺點也顯而易見:
組合不靈活,如果需要增加 2 個種 config 組合的便利方法,需要增加新方法;
擴展不便,如果全能方法增加了新的配置 configD 需要在現(xiàn)有便利方法的基礎(chǔ)上增加更多的便利方法;
鏈?zhǔn)奖磉_(dá)式
今天突然靈光一閃,想到可以用鏈?zhǔn)奖磉_(dá)式解決這個問題。在 iOS 開發(fā)領(lǐng)域因為 Masonry 這個庫的影響,鏈?zhǔn)奖磉_(dá)式知名度已經(jīng)很高,因此這篇文章不再贅述其原理。直接進入主題:首先聲明一個鏈?zhǔn)奖磉_(dá)式的鏈扣類:
.h
@interface TTDoSomethingMaker:NSObject //這里命名也參考 Masonry, 定義為 Maker
// 聲明需要的 block 屬性,包括 Name 和 Config
@property (nonatomic, copy, readonly) TTDoSomethingMaker *(^name)(NSString *name);
@property (nonatomic, copy, readonly) TTDoSomethingMaker *(^configA)(ConfigA configA);
@property (nonatomic, copy, readonly) TTDoSomethingMaker *(^configB)(ConfigB configB);
@property (nonatomic, copy, readonly) TTDoSomethingMaker *(^configC)(ConfigC configC);
@end
.m
@interface TTDoSomethingMaker ()
// 在.m 中聲明保存 name 和 config 的屬性,這里不需要暴露給外部,所以不需要聲明在 .h
@property(nonatomic, copy) NSString *nameValue;
@property(nonatomic, assign) ConfigA configAValue;
@property(nonatomic, assign) ConfigB configBValue;
@property(nonatomic, assign) ConfigC configCValue;
@end
@implementation TTDoSomethingMaker
// 實現(xiàn)所有的 block getter
(NSString *)name {
return ^TTDoSomethingMaker *(NSString *name) {
self.nameValue = name;
return self;
};
}(ConfigA)configA {
return ^TTDoSomethingMaker *(ConfigA configA) {
self.configAValue = configA;
return self;
};
}(ConfigB)configB {
return ^TTDoSomethingMaker *(ConfigB configB) {
self.configBValue = configB;
return self;
};
}(ConfigC)configC {
return ^TTDoSomethingMaker *(ConfigC configC) {
self.configCValue = configC;
return self;
};
}
@end
然后在提供功能的 TTDoSomething 類中增加一個方法:
- (void)doSomething:(void(^)(TTDoSomethingMaker *maker))block {
TTDoSomethingMaker *maker = TTDoSomethingMaker.new;
block ?: block(maker);
// todo
[self doSomethingWithName:maker.nameValue configA:maker.configAValue configB:configBValue configC:configCValue]
}
于是當(dāng)我們這樣調(diào)用:
[TTDoSomething doSomething:^(TTDoSomethingMaker *maker) {
maker.name(@"Demo").configA(ConfigAOne);
}];
就相當(dāng)于調(diào)用了
[TTDoSomething doSomethingWithName:@"Demo" configA:ConfigAOne configB:0 configC:0];
如果此時想要將 ConfigB 配置為 ConfigBOne, 只需要在原有的鏈?zhǔn)奖磉_(dá)式后面加一個 .configB(ConfigBOne) 即可。而如果想給萬能方法增加一個 ConfigD, 也只需要修改一下 doSomething: 和 TTDoSomethingMaker 的實現(xiàn),完美地遵循了開閉原則。
小優(yōu)化一發(fā)
上面 doSomething: 的實現(xiàn)里我加了一個 // todo 的注釋,在這個位置可以做一些默認(rèn)實現(xiàn),比如說合法性判斷和默認(rèn)值:
- (void)doSomething:(void(^)(TTDoSomethingMaker *maker))block {
TTDoSomethingMaker *maker = TTDoSomethingMaker.new;
!block ?: block(maker);
NSAssert(maker.nameValue, @"Parameter 'name' should not be nil.");
// 將 ConfigB 配置為 ConfigBTwo
if (maker.configBValue == 0) {
maker.configBValue = ConfigBTwo;
}
[self doSomethingWithName:maker.nameValue configA:maker.configAValue configB:configBValue configC:configCValue]
}
另外,上面的 block getter 可以封裝一個宏,減少重復(fù)性的代碼:
define TTDoSomethingMakerProperty(TYPE, NAME) - (TTDoSomethingMaker *(^)(TYPE))NAME {\
return ^TTDoSomethingMaker *(TYPE NAME) {
self.NAME##Value = NAME;
return self;
};
}\
@implementation TTDoSomethingMaker
TTDoSomethingMakerProperty(NSString *, name)
TTDoSomethingMakerProperty(ConfigA, configA)
TTDoSomethingMakerProperty(ConfigB, configB)
TTDoSomethingMakerProperty(ConfigC, configC)
@end
總結(jié)
Masonry 利用鏈?zhǔn)奖磉_(dá)式將繁瑣的 AutoLayout API 優(yōu)化成了更表意、靈活、簡潔的語法。而鏈?zhǔn)奖磉_(dá)式可以隨意自由組合的特性,恰好也是 Objective-C 不支持方法默認(rèn)參數(shù)的優(yōu)雅解決方式,雖然和 Masonry 的使用場景不一樣,但是仔細(xì)想想,這兩個場景都是對開閉原則的實現(xiàn)。