8 次嘗試聘殖,帶你走進(jìn) iOS 精益編程

作者:曉月 授權(quán)本站轉(zhuǎn)載凡人。
開場
今天, 我們將從一個(gè)小功能開始, 先去不假思索的實(shí)現(xiàn)它
Product Repository: Filtering Operation
Code start
有一個(gè)產(chǎn)品庫, 我們要對(duì)它做過濾操作.
第一個(gè)需求并不復(fù)雜.
需求1:在倉庫中查找所有顏色為紅色的產(chǎn)品
First Attempt: Hard Code
我們先用最簡單的方式去實(shí)現(xiàn)它, 硬編碼

  - (NSArray *)findAllRedProducts:(NSArray *)products{
    NSMutableArray *list = [@[] mutableCopy];
    for (Product *product in products) {
      if (product.color == RED) {
        [list addObject:product];
        }
      }
           return list;
    }

如果這個(gè)世界是永恒靜止的锋谐,這樣的實(shí)現(xiàn)無可厚非详拙,但世界往往并非如此帝际。
緊接著,第二個(gè)需求來了
需求2:在倉庫中查找所有顏色為綠色的產(chǎn)品
Second Attempt: Parameterizing
Copy-Paste是大部分程序員最容易犯的毛病,為此引入了大量的重復(fù)代碼饶辙。
- (NSArray *)findAllGreenProducts:(NSArray *)products {
NSMutableArray *list = [@[] mutableCopy];
for (Product *product in products) {
if (product.color == GREEN) {
[list addObject:product];
}
}
return list;
}
為了消滅硬編碼蹲诀,得到可重用的代碼,可以引入簡單的參數(shù)化設(shè)計(jì)畸悬。
- (NSArray *)findProducts:(NSArray *)products byColor:(ProductColor)color {
NSMutableArray *list = [@[] mutableCopy];
for (Product *product in products) {
if (product.color == color) {
[list addObject:product];
}
}
return list;
}
終于可以放心了, 這個(gè)時(shí)候我們的產(chǎn)品經(jīng)理怎么可能讓你舒服呢,需求3又來了
需求3:查找所有重量小于10的所有產(chǎn)品
Third Attempt: Parameterizing with Every Attribute You Can Think Of
大部分程序員依然會(huì)使用Copy-Paste解決這個(gè)問題侧甫,拒絕Copy-Paste的陋習(xí),最具實(shí)效的一個(gè)反饋就是讓這個(gè)快捷鍵失效蹋宦,從而在每次嘗試Copy-Paste時(shí)提醒自己做更好的設(shè)計(jì)披粟。
- (NSArray *)findProducts:(NSArray *)products byWeith:(float)weight{
NSMutableArray *list = [@[] mutableCopy];
for (Product *product in products) {
if (product.weight < weight) {
[list addObject:product];
}
}
return list;
}
為了消除兩者重復(fù)的代碼,通過簡單的參數(shù)化往往不能完美解決這類問題冷冗,相反地會(huì)引入過度的復(fù)雜度和偶發(fā)成本守屉。
- (NSArray *)findProducts:(NSArray *)products
byColor:(ProductColor)color
byWeith:(float)weight
type:(int)type{
NSMutableArray *list = [@[] mutableCopy];
for (Product *product in products) {
if ((type == 1) && product.color == color) {
[list addObject:product];
continue;
}
else if ((type == 2) && (product.weight < weight)) {
[list addObject:product];
continue;
}
}
return list;
}
日常工作中,這樣的實(shí)現(xiàn)手法非常普遍蒿辙,函數(shù)的參數(shù)列表隨著需求增加不斷增加拇泛,函數(shù)邏輯承擔(dān)的職責(zé)越來越多,邏輯也變得越來越難以控制思灌。
通過參數(shù)配置應(yīng)對(duì)變化的設(shè)計(jì)往往都是失敗的設(shè)計(jì)
易于導(dǎo)致復(fù)雜的邏輯控制俺叭,引發(fā)額外的偶發(fā)復(fù)雜度
Forth Attempt: Abstracting over Criteria
為此需要抽象,使其遍歷的算法與查找的標(biāo)準(zhǔn)能夠獨(dú)立地變化泰偿,互不影響熄守。
@interface ProductSpec : NSObject
- (BOOL)satisfy:(Product *)product;
@end
此刻filter的算法邏輯得到封閉,當(dāng)然函數(shù)名需要重命名,使其算法實(shí)現(xiàn)更加具有普遍性裕照。
- (NSArray *)findProducts:(NSArray *)products bySpec:(ProductSpec *)spec
{
NSMutableArray *list = [@[] mutableCopy];
for (Product *product in products) {
if ([spec satisfy:product]) {
[list addObject:product];
}
}
return list;
}
通過可復(fù)用的類來封裝各種變化攒发,讓變化的因素控制在最小的范圍內(nèi)。

@interface ColorSpec()
@property (nonatomic, assign) ProductColor color;
@end
@implementation ColorSpec
+ (instancetype)specWithColor:(ProductColor)color{
  ColorSpec *spec = [[ColorSpec alloc] init];
  spec.color = color;
  return spec;
}
- (BOOL)satisfy:(Product *)product  {
  return product.color == RED;
}
@end
@interface BelowWeightSpec()
@property (nonatomic, assign) float limit;
@end
@implementation BelowWeightSpec
+ (instancetype)specWithBelowWeight:(float)limit {
  BelowWeightSpec *spec = [[BelowWeightSpec alloc] init];
  spec.limit = limit;
  return spec;
}
- (BOOL)satisfy:(Product *)product{
  return (product.weight < _limit);
}
@end

用戶的接口也變得簡單多了晋南,而且富有表現(xiàn)力惠猿。

[self findProducts:_products bySpec:[ColorSpec specWithColor:RED]];

這是經(jīng)典的OO設(shè)計(jì),如果熟悉設(shè)計(jì)模式的讀者對(duì)此已經(jīng)習(xí)以為常了负间。設(shè)計(jì)模式是好東西偶妖,但往往被濫用。為此不能依葫蘆畫瓢唉擂,死板照抄餐屎,而是為了得到更簡單的設(shè)計(jì)而引入設(shè)計(jì)模式的,這個(gè)過程是很自然的玩祟。
與大師們交流腹缩,問究此處為何引入設(shè)計(jì)模式,得到的答案:直覺空扎。忘記所有設(shè)計(jì)模式吧藏鹊,管它是不是模式,如果設(shè)計(jì)是簡單的转锈,這就是模式盘寡。
另外還有一個(gè)明顯的壞味道,ColorSpec和BelowWeightSpec都需要繼承ProductSpec撮慨,都需要定義一個(gè)構(gòu)造函數(shù)和一個(gè)私有的字段竿痰,并重寫satisfy方法,這些都充斥著重復(fù)的結(jié)構(gòu)砌溺。
是不是覺得目前的寫法已經(jīng)夠用了? 莫急, 讓我們來看看下個(gè)需求
需求4:查找所有顏色為紅色影涉,并且重量小于10的所有產(chǎn)品
Firth Attempt: Composite Criteria
按照既有的代碼結(jié)構(gòu),往往易于設(shè)計(jì)出類似ColorAndBelowWeightSpec的實(shí)現(xiàn)规伐。

@interface ColorAndBelowWeigthSpec()
@property (nonatomic, assign) ProductColor color;
@property (nonatomic, assign) float limit;
@end
@implementation ColorAndBelowWeigthSpec
+ (instancetype)specWithColor:(ProductColor)color beloWeigth:(float)limit{
  ColorAndBelowWeigthSpec *spec = [[ColorAndBelowWeigthSpec alloc] init];
  spec.color = color;
  spec.limit = limit;
  return spec;
}
- (BOOL)satisfy:(Product *)product{
  return product.color == _color || (product.weight < _limit);
}
@end

存在兩個(gè)明顯的壞味道:
包含and的命名往往是違背單一職責(zé)的信號(hào)燈
ColorAndBelowWeightSpec的實(shí)現(xiàn)與ColorSpec蟹倾,BelowWeightSpec之間存在明顯的重復(fù)
此刻,需要尋找更本質(zhì)的抽象來表達(dá)設(shè)計(jì)猖闪,and/or/not語義可以完美解決這類問題鲜棠。

Composite Spec: AndSpec, OrSpec, NotSpec
Atomic Spec:ColorSpec, BeblowWeightSpec
@interface AndSpec()
@property (nonatomic, strong) NSArray *specs;
@end
@implementation AndSpec
+ (instancetype)spec:(ProductSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION{
  va_list args;
  va_start( args, spec );
  NSMutableArray *mArray = [@[spec] mutableCopy];
  for ( ;; ){
    id tempSpec = va_arg( args, id );
    if (tempSpec == nil)
      break;
      [mArray addObject:tempSpec];
    }
  va_end( args );
  AndSpec *andSpec = [[AndSpec alloc] init];
  andSpec.specs = [mArray copy];
  return andSpec;
}
- (BOOL)satisfy:(Product *)product{
  for (ProductSpec *spec in _specs) {
    if (![spec satisfy:product]) {
      return NO;
    }
  }
  return YES;
}
@end
@interface OrSpec ()
@property (nonatomic, strong) NSArray *specs;
@end
@implementation OrSpec
+ (instancetype)spec:(ProductSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION
{
  va_list args;
  va_start( args, spec );
  NSMutableArray *mArray = [@[spec] mutableCopy];
  for ( ;; ){
    id tempSpec = va_arg( args, id );
    if (tempSpec == nil)
      break;
      [mArray addObject:tempSpec];
    }
  va_end( args );
  OrSpec *orSpec = [[OrSpec alloc] init];
  orSpec.specs = [mArray copy];
  return orSpec;
}
- (BOOL)satisfy:(Product *)product{
  for (ProductSpec *spec in _specs) {
    if ([spec satisfy:product]) {
    return YES;
    }
  }
  return NO;
}
@end
@interface NotSpec ()
@property (nonatomic, strong) ProductSpec *spec;
@end
@implementation NotSpec
+ (instancetype)spec:(ProductSpec *)spec{
  NotSpec *notSpec = [[NotSpec alloc] init];
  notSpec.spec = spec;
  return notSpec;
}
- (BOOL)satisfy:(Product *)product{
  if (![_spec satisfy:product]) {
    return YES;
  }
  return NO;
}
@end

可以通過AndSpec組合ColorSpec, BelowWeightSpec來實(shí)現(xiàn)需求,簡單漂亮培慌,并且富有表達(dá)力豁陆。

[self findProducts:_products bySpec:[AndSpec spec:[ColorSpec     specWithColor:RED], [BelowWeightSpec specWithBelowWeight:10], nil]];

1
但這樣的設(shè)計(jì)存在兩個(gè)嚴(yán)重的壞問道:
AndSpec與OrSpec存在明顯的代碼重復(fù),OO設(shè)計(jì)的第一個(gè)直覺就是通過抽取基類來消除重復(fù)吵护。

@interface CombinableSpec ()
@property (nonatomic, strong) NSArray *specs;
@end
@implementation CombinableSpec
+ (instancetype)spec:    (CombinableSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION{
  va_list args;
  va_start( args, spec );
  NSMutableArray *mArray = [@[spec] mutableCopy];
  for ( ;; ){
    id tempSpec = va_arg( args, id );
    if (tempSpec == nil){
      break;
      [mArray addObject:tempSpec];
    }
  }
  va_end( args );
  CombinableSpec *combinableSpec = [[CombinableSpec alloc] init];
  combinableSpec.specs = [mArray copy];
  return combinableSpec;
}
- (BOOL)satisfy:(Product *)product{
  for (ProductSpec *spec in _specs) {
    if ([spec satisfy:product] == _shortcut) {
    return _shortcut;
    }
  }
return !_shortcut;
}
@end
@implementation AndSpec
- (instancetype)init {
  self = [super init];
  if (self) {
    self.shortcut = NO;
  }
  return self;
}
@end
@implementation OrSpec
- (instancetype)init{
  self = [super init];
  if (self) {
    self.shortcut = YES;
  }
  return self;
}
@end

大堆的初始化方法讓人眼花繚亂

[self findProducts:_products bySpec:[NotSpec spec:[AndSpec spec:[ColorSpec specWithColor:RED], [BelowWeightSpec specWithBelowWeight:10], nil]]];

Sixth Attempt: Using DSL
可以引入DSL改善程序的可讀性盒音,讓代碼更具表達(dá)力竖配。
我們先添加一些DSL:

static ProductSpec *COLOR(ProductColor color){
  return [ColorSpec specWithColor:RED];
}
static ProductSpec *BELOWWEIGHT(float limit)  {
  return [BelowWeightSpec specWithBelowWeight:limit];
}
static ProductSpec *AND(ProductSpec *spec1, ProductSpec *spec2){
  return [AndSpec spec:spec1, spec2, nil];
}
static ProductSpec *OR(ProductSpec *spec1, ProductSpec *spec2){
  return [OrSpec spec:spec1, spec2, nil];
}
static ProductSpec *NOT(ProductSpec *spec){
  return [NotSpec spec:spec];
}

這樣我們的代碼表現(xiàn)起來就是這樣的

[self findProducts:_products bySpec:NOT(AND(COLOR(RED), BELOWWEIGHT(10)))];

Seventh Attempt: Using a Lambda Expression
可以使用Block改善設(shè)計(jì),增強(qiáng)表達(dá)力里逆。

- (NSArray *)findProducts:(NSArray *)products byBlock:(BOOL (^)())block{
  NSMutableArray *list = [@[] mutableCopy];
  for (Product *product in products) {
    if (block(product)) {
      [list addObject:product];
    }
  }
  return list;
}

代碼現(xiàn)在開起來是這個(gè)樣子

[self findProducts:_products byBlock:^BOOL(id p) {return [p color] == RED;}];

構(gòu)造DSL,復(fù)用這些Block

ProductSpecBlock color(ProductColor color){
  return ^BOOL(id p) {return [p color] == color;};
}
ProductSpecBlock weightBelow(float limit){
  return ^BOOL(id p) {return [p weight] < limit;};
}
- (void)test7_2{
  [self findProducts:_products byBlock:color(RED)];
}

Eighth attempt: Using NSPredicate
還可以使用標(biāo)準(zhǔn)庫

[self.products filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"weight > 10"]];

結(jié)束
今天的編碼就到此為止了, 這篇文章本是Horance所寫, 筆者將用OC實(shí)現(xiàn)了一遍.如果咱們不是iOS Developer的話, 還是有其他attempt的, 如泛型.
作者介紹
劉光聰用爪,程序員原押,敏捷教練,開源軟件愛好者偎血,目前供職于中興通訊無線研究院诸衔,具有多年大型遺留系統(tǒng)的重構(gòu)經(jīng)驗(yàn),對(duì)面向?qū)ο笃溺瑁瘮?shù)式笨农,大數(shù)據(jù)等領(lǐng)域具有濃厚的興趣。
github:https://github.com/horance-liu
email: horance@outlook.com
邢堯, 資深開發(fā)工程師, iOS Developer, 開源軟件愛好者, 追求真理比占有真理更加難能可貴
Github:https://github.com/uxyheaven
Blog:http://blog.csdn.bet/uxyheaven

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帖渠,一起剝皮案震驚了整個(gè)濱河市谒亦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌空郊,老刑警劉巖份招,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異狞甚,居然都是意外死亡锁摔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門哼审,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谐腰,“玉大人,你說我怎么就攤上這事涩盾∈” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵旁赊,是天一觀的道長桦踊。 經(jīng)常有香客問我,道長终畅,這世上最難降的妖魔是什么籍胯? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮离福,結(jié)果婚禮上杖狼,老公的妹妹穿的比我還像新娘。我一直安慰自己妖爷,他們只是感情好蝶涩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布理朋。 她就那樣靜靜地躺著,像睡著了一般绿聘。 火紅的嫁衣襯著肌膚如雪嗽上。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天熄攘,我揣著相機(jī)與錄音兽愤,去河邊找鬼。 笑死挪圾,一個(gè)胖子當(dāng)著我的面吹牛浅萧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哲思,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼洼畅,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了棚赔?” 一聲冷哼從身側(cè)響起帝簇,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忆嗜,沒想到半個(gè)月后己儒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捆毫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年闪湾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绩卤。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡途样,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出濒憋,到底是詐尸還是另有隱情何暇,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布凛驮,位于F島的核電站裆站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏黔夭。R本人自食惡果不足惜宏胯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望本姥。 院中可真熱鬧肩袍,春花似錦、人聲如沸婚惫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至艰管,卻和暖如春滓侍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牲芋。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國打工粗井, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人街图。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像懒构,于是被迫代替她去往敵國和親餐济。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,167評(píng)論 25 707
  • OO makes code understandable by encapsulating moving part...
    劉光聰閱讀 957評(píng)論 0 6
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理胆剧,服務(wù)發(fā)現(xiàn)絮姆,斷路器,智...
    卡卡羅2017閱讀 134,661評(píng)論 18 139
  • 今天秩霍,也算我第一次真正的使用思維導(dǎo)圖吧篙悯。藥物化學(xué)抗腫瘤那部分,上完課其實(shí)我就有做筆記铃绒,把所有要記得和要知道的全部...
    Catherinein閱讀 376評(píng)論 0 0
  • 雖然買房不能像買白菜那樣當(dāng)時(shí)就買走鸽照,但卻是可以一樣砍價(jià)的。其實(shí)買房的過程就跟做生意一樣颠悬,買房談判時(shí)矮燎,要懂得把握對(duì)方...
    喜馬拉雅鐵鍬閱讀 429評(píng)論 0 0