当前位置: 首页>編程日記>正文

iOS 精益编程

iOS 精益编程

开场

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

  • Product Repository: Filtering Operation

Code start

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

第一个需求并不复杂.

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

First Attempt: Hard Code

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

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

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

紧接着,第二个需求来了

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

Second Attempt: Parameterizing

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

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

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

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

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

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

Third Attempt: Parameterizing with Every Attribute You Can Think Of

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

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

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

- (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;
}

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

  • 通过参数配置应对变化的设计往往都是失败的设计
  • 易于导致复杂的逻辑控制,引发额外的偶发复杂度

Forth Attempt: Abstracting over Criteria

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

@interface ProductSpec : NSObject
- (BOOL)satisfy:(Product *)product;
@end

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

- (NSArray *)findProducts:(NSArray *)products bySpec:(ProductSpec *)spec
{NSMutableArray *list = [@[] mutableCopy];for (Product *product in products) {if ([spec satisfy:product]) {[list addObject:product];}}
    return list;
}

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

@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

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

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

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

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

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

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

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

Firth Attempt: Composite Criteria

按照既有的代码结构,往往易于设计出类似ColorAndBelowWeightSpec的实现。

@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

存在两个明显的坏味道:

  • 包含and的命名往往是违背单一职责的信号灯
  • ColorAndBelowWeightSpec的实现与ColorSpecBelowWeightSpec之间存在明显的重复

此刻,需要寻找更本质的抽象来表达设计,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来实现需求,简单漂亮,并且富有表达力。

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

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

  • AndSpecOrSpec存在明显的代码重复,OO设计的第一个直觉就是通过抽取基类来消除重复。
@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 = [[self 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改善程序的可读性,让代码更具表达力。

我们先添加一些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];
}

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

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

Seventh Attempt: Using a Lambda Expression

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

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

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

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

构造DSL,复用这些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

还可以使用标准库

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

结束

Dome在 WriteLeanPrograms目录下

https://github.com/uxyheaven/SomeDemo

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

作者介绍

  • 刘光聪,程序员,敏捷教练,开源软件爱好者,目前供职于中兴通讯无线研究院,具有多年大型遗留系统的重构经验,对面向对象,函数式,大数据等领域具有浓厚的兴趣。
    • github: https://github.com/horance-liu
    • email: horance@outlook.com
  • 邢尧, 资深开发工程师, iOS Developer, 开源软件爱好者, 追求真理比占有真理更加难能可贵
    • Github: https://github.com/uxyheaven
    • Blog: http://blog.csdn.bet/uxyheaven


https://www.fengoutiyan.com/post/15236.html

相关文章:

  • iphone上的编程软件
  • ipad编程软件
  • ios 开发
  • iOS平台编程
  • iOS编程手册
  • iOS组件化开发
  • ios编程题
  • iphone手机编程软件
  • 鏡像模式如何設置在哪,圖片鏡像操作
  • 什么軟件可以把圖片鏡像翻轉,C#圖片處理 解決左右鏡像相反(旋轉圖片)
  • 手機照片鏡像翻轉,C#圖像鏡像
  • 視頻鏡像翻轉軟件,python圖片鏡像翻轉_python中鏡像實現方法
  • 什么軟件可以把圖片鏡像翻轉,利用PS實現圖片的鏡像處理
  • 照片鏡像翻轉app,java實現圖片鏡像翻轉
  • 什么軟件可以把圖片鏡像翻轉,python圖片鏡像翻轉_python圖像處理之鏡像實現方法
  • matlab下載,matlab如何鏡像處理圖片,matlab實現圖像鏡像
  • 圖片鏡像翻轉,MATLAB:鏡像圖片
  • 鏡像翻轉圖片的軟件,圖像處理:實現圖片鏡像(基于python)
  • canvas可畫,JavaScript - canvas - 鏡像圖片
  • 圖片鏡像翻轉,UGUI優化:使用鏡像圖片
  • Codeforces,CodeForces 1253C
  • MySQL下載安裝,Mysql ERROR: 1253 解決方法
  • 勝利大逃亡英雄逃亡方案,HDU - 1253 勝利大逃亡 BFS
  • 大一c語言期末考試試題及答案匯總,電大計算機C語言1253,1253《C語言程序設計》電大期末精彩試題及其問題詳解
  • lu求解線性方程組,P1253 [yLOI2018] 扶蘇的問題 (線段樹)
  • c語言程序設計基礎題庫,1253號C語言程序設計試題,2016年1月試卷號1253C語言程序設計A.pdf
  • 信奧賽一本通官網,【信奧賽一本通】1253:抓住那頭牛(詳細代碼)
  • c語言程序設計1253,1253c語言程序設計a(2010年1月)
  • 勝利大逃亡英雄逃亡方案,BFS——1253 勝利大逃亡
  • 直流電壓測量模塊,IM1253B交直流電能計量模塊(艾銳達光電)
  • c語言程序設計第三版課后答案,【渝粵題庫】國家開放大學2021春1253C語言程序設計答案
  • 18轉換為二進制,1253. 將數字轉換為16進制
  • light-emitting diode,LightOJ-1253 Misere Nim
  • masterroyale魔改版,1253 Dungeon Master
  • codeformer官網中文版,codeforces.1253 B
  • c語言程序設計考研真題及答案,2020C語言程序設計1253,1253計算機科學與技術專業C語言程序設計A科目2020年09月國家開 放大學(中央廣播電視大學)
  • c語言程序設計基礎題庫,1253本科2016c語言程序設計試題,1253電大《C語言程序設計A》試題和答案200901
  • 肇事逃逸車輛無法聯系到車主怎么辦,1253尋找肇事司機