8次尝试,带你走进iOS 精益编程

移动开发 iOS
Copy-Paste是大部分程序员最容易犯的毛病,为此引入了大量的重复代码。

[[153315]]

开场

今天, 我们将从一个小功能开始, 先去不假思索的实现它

Product Repository: Filtering Operation

Code start

有一个产品库, 我们要对它做过滤操作.

***个需求并不复杂.

需求1:在仓库中查找所有颜色为红色的产品

First Attempt: Hard Code

我们先用最简单的方式去实现它, 硬编码

  1. - (NSArray *)findAllRedProducts:(NSArray *)products 
  2. NSMutableArray *list = [@[] mutableCopy]; 
  3. for (Product *product in products) { 
  4. if (product.color == RED) { 
  5. [list addObject:product]; 
  6. return list; 

 

如果这个世界是永恒静止的,这样的实现无可厚非,但世界往往并非如此。

紧接着,第二个需求来了

需求2:在仓库中查找所有颜色为绿色的产品

Second Attempt: Parameterizing

Copy-Paste是大部分程序员最容易犯的毛病,为此引入了大量的重复代码。

  1. - (NSArray *)findAllGreenProducts:(NSArray *)products 
  2. NSMutableArray *list = [@[] mutableCopy]; 
  3. for (Product *product in products) { 
  4. if (product.color == GREEN) { 
  5. [list addObject:product]; 
  6. return list; 

为了消灭硬编码,得到可重用的代码,可以引入简单的参数化设计。

  1. - (NSArray *)findProducts:(NSArray *)products byColor:(ProductColor)color 
  2. NSMutableArray *list = [@[] mutableCopy]; 
  3. for (Product *product in products) { 
  4. if (product.color == color) { 
  5. [list addObject:product]; 
  6. return list; 

终于可以放心了, 这个时候我们的产品经理怎么可能让你舒服呢,需求3又来了

需求3:查找所有重量小于10的所有产品

Third Attempt: Parameterizing with Every Attribute You Can Think Of

大部分程序员依然会使用Copy-Paste解决这个问题,拒绝Copy-Paste的陋习,***实效的一个反馈就是让这个快捷键失效,从而在每次尝试Copy-Paste时提醒自己做更好的设计

  1. - (NSArray *)findProducts:(NSArray *)products byWeith:(float)weight 
  2. NSMutableArray *list = [@[] mutableCopy]; 
  3. for (Product *product in products) { 
  4. if (product.weight < weight) { 
  5. [list addObject:product]; 
  6. return list; 

为了消除两者重复的代码,通过简单的参数化往往不能***解决这类问题,相反地会引入过度的复杂度和偶发成本。

  1. - (NSArray *)findProducts:(NSArray *)products byColor:(ProductColor)color byWeith:(float)weight type:(int)type 
  2. NSMutableArray *list = [@[] mutableCopy]; 
  3. for (Product *product in products) { 
  4. if ((type == 1) && product.color == color) { 
  5. [list addObject:product]; 
  6. continue
  7. else if ((type == 2) && (product.weight < weight)) 
  8. [list addObject:product]; 
  9. continue
  10. return list; 

日常工作中,这样的实现手法非常普遍,函数的参数列表随着需求增加不断增加,函数逻辑承担的职责越来越多,逻辑也变得越来越难以控制。

通过参数配置应对变化的设计往往都是失败的设计

易于导致复杂的逻辑控制,引发额外的偶发复杂度

Forth Attempt: Abstracting over Criteria

为此需要抽象,使其遍历的算法与查找的标准能够独立地变化,互不影响。

  1. @interface ProductSpec : NSObject 
  2. - (BOOL)satisfy:(Product *)product; 
  3. @end 

此刻filter的算法逻辑得到封闭,当然函数名需要重命名,使其算法实现更加具有普遍性。

  1. - (NSArray *)findProducts:(NSArray *)products bySpec:(ProductSpec *)spec 
  2. NSMutableArray *list = [@[] mutableCopy]; 
  3. for (Product *product in products) { 
  4. if ([spec satisfy:product]) { 
  5. [list addObject:product]; 
  6. return list; 

通过可复用的类来封装各种变化,让变化的因素控制在最小的范围内。

  1. @interface ColorSpec() 
  2. @property (nonatomic, assign) ProductColor color; 
  3. @end 
  4. @implementation ColorSpec 
  5. + (instancetype)specWithColor:(ProductColor)color 
  6. ColorSpec *spec = [[ColorSpec alloc] init]; 
  7. spec.color = color; 
  8. return spec; 
  9. - (BOOL)satisfy:(Product *)product 
  10. return product.color == RED; 
  11. @end 
  12. @interface BelowWeightSpec() 
  13. @property (nonatomic, assign) float limit; 
  14. @end 
  15. @implementation BelowWeightSpec 
  16. + (instancetype)specWithBelowWeight:(float)limit 
  17. BelowWeightSpec *spec = [[BelowWeightSpec alloc] init]; 
  18. spec.limit = limit; 
  19. return spec; 
  20. - (BOOL)satisfy:(Product *)product 
  21. return (product.weight < _limit); 
  22. @end 

用户的接口也变得简单多了,而且富有表现力。

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

这是经典的OO设计,如果熟悉设计模式的读者对此已经习以为常了。设计模式是好东西,但往往被滥用。为此不能依葫芦画瓢,死板照抄,而是为了得到更简单的设计而引入设计模式的,这个过程是很自然的。

与大师们交流,问究此处为何引入设计模式,得到的答案:直觉。忘记所有设计模式吧,管它是不是模式,如果设计是简单的,这就是模式。

另外还有一个明显的坏味道,ColorSpec和BelowWeightSpec都需要继承ProductSpec,都需要定义一个构造函数和一个私有的字段,并重写satisfy方法,这些都充斥着重复的结构。

是不是觉得目前的写法已经够用了? 莫急, 让我们来看看下个需求

需求4:查找所有颜色为红色,并且重量小于10的所有产品

 

  1. Firth Attempt: Composite Criteria 
  2.  
  3. 按照既有的代码结构,往往易于设计出类似ColorAndBelowWeightSpec的实现。 
  4. @interface ColorAndBelowWeigthSpec() 
  5. @property (nonatomic, assign) ProductColor color; 
  6. @property (nonatomic, assign) float limit; 
  7. @end 
  8. @implementation ColorAndBelowWeigthSpec 
  9. + (instancetype)specWithColor:(ProductColor)color beloWeigth:(float)limit 
  10. ColorAndBelowWeigthSpec *spec = [[ColorAndBelowWeigthSpec alloc] init]; 
  11. spec.color = color; 
  12. spec.limit = limit; 
  13. return spec; 
  14. - (BOOL)satisfy:(Product *)product 
  15. return product.color == _color || (product.weight < _limit); 
  16. @end 

 

存在两个明显的坏味道:

包含and的命名往往是违背单一职责的信号灯

ColorAndBelowWeightSpec的实现与ColorSpec,BelowWeightSpec之间存在明显的重复

此刻,需要寻找更本质的抽象来表达设计,and/or/not语义可以***解决这类问题。

Composite Spec: AndSpec, OrSpec, NotSpec

Atomic Spec:ColorSpec, BeblowWeightSpec

  1. @interface AndSpec() 
  2. @property (nonatomic, strong) NSArray *specs; 
  3. @end 
  4. @implementation AndSpec 
  5. + (instancetype)spec:(ProductSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION 
  6. va_list args; 
  7. va_start( args, spec ); 
  8. NSMutableArray *mArray = [@[spec] mutableCopy]; 
  9. for ( ;; ) 
  10. id tempSpec = va_arg( args, id ); 
  11. if (tempSpec == nil) 
  12. break
  13. [mArray addObject:tempSpec]; 
  14. va_end( args ); 
  15. AndSpec *andSpec = [[AndSpec alloc] init]; 
  16. andSpec.specs = [mArray copy]; 
  17. return andSpec; 
  18. - (BOOL)satisfy:(Product *)product 
  19. for (ProductSpec *spec in _specs) { 
  20. if (![spec satisfy:product]) { 
  21. return NO; 
  22. return YES; 
  23. @end 
  24. @interface OrSpec () 
  25. @property (nonatomic, strong) NSArray *specs; 
  26. @end 
  27. @implementation OrSpec 
  28. + (instancetype)spec:(ProductSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION 
  29. va_list args; 
  30. va_start( args, spec ); 
  31. NSMutableArray *mArray = [@[spec] mutableCopy]; 
  32. for ( ;; ) 
  33. id tempSpec = va_arg( args, id ); 
  34. if (tempSpec == nil) 
  35. break
  36. [mArray addObject:tempSpec]; 
  37. va_end( args ); 
  38. OrSpec *orSpec = [[OrSpec alloc] init]; 
  39. orSpec.specs = [mArray copy]; 
  40. return orSpec; 
  41. - (BOOL)satisfy:(Product *)product 
  42. for (ProductSpec *spec in _specs) { 
  43. if ([spec satisfy:product]) { 
  44. return YES; 
  45. return NO; 
  46. @end 
  47. @interface NotSpec () 
  48. @property (nonatomic, strong) ProductSpec *spec; 
  49. @end 
  50. @implementation NotSpec 
  51. + (instancetype)spec:(ProductSpec *)spec 
  52. NotSpec *notSpec = [[NotSpec alloc] init]; 
  53. notSpec.spec = spec; 
  54. return notSpec; 
  55. - (BOOL)satisfy:(Product *)product 
  56. if (![_spec satisfy:product]) { 
  57. return YES; 
  58. return NO; 
  59. @end 

可以通过AndSpec组合ColorSpec, BelowWeightSpec来实现需求,简单漂亮,并且富有表达力。

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

但这样的设计存在两个严重的坏问道:

AndSpec与OrSpec存在明显的代码重复,OO设计的***个直觉就是通过抽取基类来消除重复。

  1. @interface CombinableSpec () 
  2. @property (nonatomic, strong) NSArray *specs; 
  3. @end 
  4. @implementation CombinableSpec 
  5. + (instancetype)spec:(CombinableSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION 
  6. va_list args; 
  7. va_start( args, spec ); 
  8. NSMutableArray *mArray = [@[spec] mutableCopy]; 
  9. for ( ;; ) 
  10. id tempSpec = va_arg( args, id ); 
  11. if (tempSpec == nil) 
  12. break
  13. [mArray addObject:tempSpec]; 
  14. va_end( args ); 
  15. CombinableSpec *combinableSpec = [[CombinableSpec alloc] init]; 
  16. combinableSpec.specs = [mArray copy]; 
  17. return combinableSpec; 
  18. - (BOOL)satisfy:(Product *)product 
  19. for (ProductSpec *spec in _specs) { 
  20. if ([spec satisfy:product] == _shortcut) { 
  21. return _shortcut; 
  22. return !_shortcut; 
  23. @end 
  24. @implementation AndSpec 
  25. - (instancetype)init 
  26. self = [super init]; 
  27. if (self) { 
  28. self.shortcut = NO; 
  29. return self; 
  30. @end 
  31. @implementation OrSpec 
  32. - (instancetype)init 
  33. self = [super init]; 
  34. if (self) { 
  35. self.shortcut = YES; 
  36. return self; 
  37. @end 

大堆的初始化方法让人眼花缭乱

  1. [self findProducts:_products bySpec:[NotSpec spec:[AndSpec spec:[ColorSpec specWithColor:RED], [BelowWeightSpec specWithBelowWeight:10], nil]]]; 
  2.  
  3. Sixth Attempt: Using DSL 

 

可以引入DSL改善程序的可读性,让代码更具表达力。

我们先添加一些DSL:

  1. static ProductSpec *COLOR(ProductColor color) 
  2. return [ColorSpec specWithColor:RED]; 
  3. static ProductSpec *BELOWWEIGHT(float limit) 
  4. return [BelowWeightSpec specWithBelowWeight:limit]; 
  5. static ProductSpec *AND(ProductSpec *spec1, ProductSpec *spec2) 
  6. return [AndSpec spec:spec1, spec2, nil]; 
  7. static ProductSpec *OR(ProductSpec *spec1, ProductSpec *spec2) 
  8. return [OrSpec spec:spec1, spec2, nil]; 
  9. static ProductSpec *NOT(ProductSpec *spec) 
  10. return [NotSpec spec:spec]; 

这样我们的代码表现起来就是这样的

  1. [self findProducts:_products bySpec:NOT(AND(COLOR(RED), BELOWWEIGHT(10)))]; 
  2.  
  3. Seventh Attempt: Using a Lambda Expression 

 

可以使用Block改善设计,增强表达力。

  1. - (NSArray *)findProducts:(NSArray *)products byBlock:(BOOL (^)())block 
  2. NSMutableArray *list = [@[] mutableCopy]; 
  3. for (Product *product in products) { 
  4. if (block(product)) { 
  5. [list addObject:product]; 
  6. return list; 

代码现在开起来是这个样子

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

构造DSL,复用这些Block

  1. ProductSpecBlock color(ProductColor color) 
  2. return ^BOOL(id p) {return [p color] == color;}; 
  3. ProductSpecBlock weightBelow(float limit) 
  4. return ^BOOL(id p) {return [p weight] < limit;}; 
  5. - (void)test7_2 
  6. [self findProducts:_products byBlock:color(RED)]; 
  7.  
  8. Eighth attempt: Using NSPredicate 

 

还可以使用标准库
 

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

结束

今天的编码就到此为止了, 这篇文章本是Horance所写, 笔者将用OC实现了一遍.如果咱们不是iOS Developer的话, 还是有其他attempt的, 如泛型.

责任编辑:chenqingxiang 来源: 晓月的专栏
相关推荐

2017-05-16 15:27:32

精益敏捷代码

2016-07-18 16:09:40

精益生产Testin质量控制

2016-02-24 16:09:24

并行科技软件交付

2010-03-16 17:30:14

Java多线程编程

2009-12-09 13:41:50

PHP Zend框架

2013-08-14 13:35:32

设计

2011-10-08 14:38:21

精益扫描仪

2017-09-21 10:34:38

留存分析数据分析留存

2011-10-06 17:10:24

精益扫描仪

2020-07-03 09:41:20

华为∑co时间医疗

2010-09-14 10:15:24

2011-03-16 14:20:30

2015-07-22 10:25:09

NEC

2011-07-27 17:05:59

精益扫描仪

2012-06-04 15:26:29

精益扫描仪

2014-03-27 17:20:02

金蝶

2015-11-03 11:08:01

IT高性能运维

2011-09-14 17:21:28

精益扫描仪

2020-08-11 09:47:30

JS闭包代码

2018-08-27 11:46:37

易观之星
点赞
收藏

51CTO技术栈公众号