开场
今天, 我们将从一个小功能开始, 先去不假思索的实现它
- Product Repository: Filtering Operation
Code start
有一个产品库, 我们要对它做过滤操作.
第一个需求并不复杂.
- 需求1:在仓库中查找所有颜色为
红色
的产品
First Attempt: Hard Code
我们先用最简单的方式去实现它, 硬编码
<code class="hljs applescript has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">- (NSArray *)findAllRedProducts:(NSArray *)products { NSMutableArray *<span class="hljs-type" style="box-sizing: border-box;">list</span> = [@[] mutableCopy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (Product *product <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> products) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (product.color == RED) { [<span class="hljs-type" style="box-sizing: border-box;">list</span> addObject:product]; } } <span class="hljs-command" style="box-sizing: border-box;"> return</span> <span class="hljs-type" style="box-sizing: border-box;">list</span>; }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>
如果这个世界是永恒静止的,这样的实现无可厚非,但世界往往并非如此。
紧接着,第二个需求来了
- 需求2:在仓库中查找所有颜色为
绿色
的产品
Second Attempt: Parameterizing
Copy-Paste
是大部分程序员最容易犯的毛病,为此引入了大量的重复代码。
<code class="hljs applescript has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">- (NSArray *)findAllGreenProducts:(NSArray *)products { NSMutableArray *<span class="hljs-type" style="box-sizing: border-box;">list</span> = [@[] mutableCopy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (Product *product <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> products) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (product.color == GREEN) { [<span class="hljs-type" style="box-sizing: border-box;">list</span> addObject:product]; } } <span class="hljs-command" style="box-sizing: border-box;"> return</span> <span class="hljs-type" style="box-sizing: border-box;">list</span>; }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>
为了消灭硬编码,得到可重用的代码,可以引入简单的参数化
设计。
<code class="hljs mel has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">- (NSArray <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*)</span>findProducts:(NSArray <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*)</span>products byColor:(ProductColor)<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">color</span> { NSMutableArray <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*list</span> = [<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">@[</span>] mutableCopy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (Product <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*product</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> products) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (product.<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">color</span> == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">color</span>) { [list addObject:product]; } } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> list; }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>
终于可以放心了, 这个时候我们的产品经理怎么可能让你舒服呢,需求3又来了
- 需求3:查找所有重量小于10的所有产品
Third Attempt: Parameterizing with Every Attribute You Can Think Of
大部分程序员依然会使用Copy-Paste
解决这个问题,拒绝Copy-Paste
的陋习,最具实效的一个反馈就是让这个快捷键失效,从而在每次尝试Copy-Paste
时提醒自己做更好的设计。
<code class="hljs ocaml has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">- (NSArray *)findProducts:(NSArray *)products byWeith:(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">float</span>)weight { NSMutableArray *<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">list</span> = [@[] mutableCopy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (Product *product <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> products) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (product.weight < weight) { [<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">list</span> addObject:product]; } } return <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">list</span>; }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>
为了消除两者重复的代码,通过简单的参数化
往往不能完美解决这类问题,相反地会引入过度的复杂度和偶发成本。
<code class="hljs ocaml has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">- (NSArray *)findProducts:(NSArray *)products byColor:(ProductColor)color byWeith:(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">float</span>)weight <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">type</span>:(</span><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">int</span>)<span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">type</span></span> { NSMutableArray *<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">list</span> = [@[] mutableCopy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (Product *product <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> products) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ((<span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">type</span> =</span>= <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>) && product.color == color) { [<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">list</span> addObject:product]; continue; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ((<span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">type</span> =</span>= <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>) && (product.weight < weight)) { [<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">list</span> addObject:product]; continue; } } return <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">list</span>; }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li></ul>
日常工作中,这样的实现手法非常普遍,函数的参数列表随着需求增加不断增加,函数逻辑承担的职责越来越多,逻辑也变得越来越难以控制。
- 通过参数配置应对变化的设计往往都是失败的设计
- 易于导致复杂的逻辑控制,引发额外的偶发复杂度
Forth Attempt: Abstracting over Criteria
为此需要抽象,使其遍历的算法与查找的标准能够独立地变化,互不影响。
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@interface</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">ProductSpec</span> : <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">NSObject</span></span> - (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span>)satisfy:(Product *)product; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>
此刻filter
的算法逻辑得到封闭,当然函数名需要重命名,使其算法实现更加具有普遍性。
<code class="hljs applescript has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">- (NSArray *)findProducts:(NSArray *)products bySpec:(ProductSpec *)spec { NSMutableArray *<span class="hljs-type" style="box-sizing: border-box;">list</span> = [@[] mutableCopy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (Product *product <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> products) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ([spec satisfy:product]) { [<span class="hljs-type" style="box-sizing: border-box;">list</span> addObject:product]; } } <span class="hljs-command" style="box-sizing: border-box;"> return</span> <span class="hljs-type" style="box-sizing: border-box;">list</span>; }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>
通过可复用的类来封装各种变化,让变化的因素控制在最小的范围内。
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@interface</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">ColorSpec</span>()</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@property</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">nonatomic</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">assign</span>) ProductColor color; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@implementation</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">ColorSpec</span></span> + (instancetype)specWithColor:(ProductColor)color { ColorSpec *spec = [[ColorSpec alloc] init]; spec<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.color</span> = color; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> spec; } - (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span>)satisfy:(Product *)product { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> product<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.color</span> == RED; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@interface</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">BelowWeightSpec</span>()</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@property</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">nonatomic</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">assign</span>) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> limit; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@implementation</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">BelowWeightSpec</span></span> + (instancetype)specWithBelowWeight:(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>)limit { BelowWeightSpec *spec = [[BelowWeightSpec alloc] init]; spec<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.limit</span> = limit; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> spec; } - (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span>)satisfy:(Product *)product { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> (product<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.weight</span> < _limit); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li></ul>
用户的接口也变得简单多了,而且富有表现力。
<code class="hljs ruby has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">[<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">findProducts:</span>_products <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">bySpec:</span>[<span class="hljs-constant" style="box-sizing: border-box;">ColorSpec</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">specWithColor:</span><span class="hljs-constant" style="box-sizing: border-box;">RED</span>]];</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
这是经典的OO
设计,如果熟悉设计模式的读者对此已经习以为常了。设计模式是好东西,但往往被滥用。为此不能依葫芦画瓢,死板照抄,而是为了得到更简单的设计而引入设计模式的,这个过程是很自然的。
与大师们交流,问究此处为何引入设计模式,得到的答案:直觉。忘记所有设计模式吧,管它是不是模式,如果设计是简单的,这就是模式。
另外还有一个明显的坏味道,ColorSpec和BelowWeightSpec都需要继承ProductSpec,都需要定义一个构造函数和一个私有的字段,并重写satisfy方法,这些都充斥着重复的结构。
是不是觉得目前的写法已经够用了? 莫急, 让我们来看看下个需求
- 需求4:查找所有颜色为红色,并且重量小于10的所有产品
Firth Attempt: Composite Criteria
按照既有的代码结构,往往易于设计出类似ColorAndBelowWeightSpec的实现。
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@interface</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">ColorAndBelowWeigthSpec</span>()</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@property</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">nonatomic</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">assign</span>) ProductColor color; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@property</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">nonatomic</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">assign</span>) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> limit; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@implementation</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">ColorAndBelowWeigthSpec</span></span> + (instancetype)specWithColor:(ProductColor)color beloWeigth:(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>)limit { ColorAndBelowWeigthSpec *spec = [[ColorAndBelowWeigthSpec alloc] init]; spec<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.color</span> = color; spec<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.limit</span> = limit; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> spec; } - (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span>)satisfy:(Product *)product { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> product<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.color</span> == _color || (product<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.weight</span> < _limit); } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li></ul>
存在两个明显的坏味道:
- 包含
and
的命名往往是违背单一职责的信号灯 ColorAndBelowWeightSpec
的实现与ColorSpec
,BelowWeightSpec
之间存在明显的重复
此刻,需要寻找更本质的抽象来表达设计,and/or/not
语义可以完美解决这类问题。
- Composite Spec: AndSpec, OrSpec, NotSpec
- Atomic Spec:ColorSpec, BeblowWeightSpec
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@interface</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">AndSpec</span>()</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@property</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">nonatomic</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">strong</span>) <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">NSArray</span> *specs; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@implementation</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">AndSpec</span></span> + (instancetype)spec:(ProductSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION { va_list args; va_start( args, spec ); <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">NSMutableArray</span> *mArray = [@[spec] mutableCopy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> ( ;; ) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">id</span> tempSpec = va_arg( args, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">id</span> ); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (tempSpec == <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">nil</span>) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>; [mArray addObject:tempSpec]; } va_end( args ); AndSpec *andSpec = [[AndSpec alloc] init]; andSpec<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.specs</span> = [mArray copy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> andSpec; } - (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span>)satisfy:(Product *)product { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (ProductSpec *spec in _specs) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (![spec satisfy:product]) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">NO</span>; } } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">YES</span>; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li></ul>
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@interface</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">OrSpec</span> ()</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@property</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">nonatomic</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">strong</span>) <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">NSArray</span> *specs; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@implementation</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">OrSpec</span></span> + (instancetype)spec:(ProductSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION { va_list args; va_start( args, spec ); <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">NSMutableArray</span> *mArray = [@[spec] mutableCopy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> ( ;; ) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">id</span> tempSpec = va_arg( args, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">id</span> ); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (tempSpec == <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">nil</span>) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>; [mArray addObject:tempSpec]; } va_end( args ); OrSpec *orSpec = [[OrSpec alloc] init]; orSpec<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.specs</span> = [mArray copy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> orSpec; } - (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span>)satisfy:(Product *)product { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (ProductSpec *spec in _specs) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ([spec satisfy:product]) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">YES</span>; } } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">NO</span>; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span> </code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li></ul>
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@interface</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">NotSpec</span> ()</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@property</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">nonatomic</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">strong</span>) ProductSpec *spec; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@implementation</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">NotSpec</span></span> + (instancetype)spec:(ProductSpec *)spec { NotSpec *notSpec = [[NotSpec alloc] init]; notSpec<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.spec</span> = spec; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> notSpec; } - (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span>)satisfy:(Product *)product { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (![_spec satisfy:product]) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">YES</span>; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">NO</span>; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span> </code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li></ul>
可以通过AndSpec
组合ColorSpec
, BelowWeightSpec
来实现需求,简单漂亮,并且富有表达力。
<code class="hljs ruby has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">[<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">findProducts:</span>_products <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">bySpec:</span>[<span class="hljs-constant" style="box-sizing: border-box;">AndSpec</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">spec:</span>[<span class="hljs-constant" style="box-sizing: border-box;">ColorSpec</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">specWithColor:</span><span class="hljs-constant" style="box-sizing: border-box;">RED</span>], [<span class="hljs-constant" style="box-sizing: border-box;">BelowWeightSpec</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">specWithBelowWeight:</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>], <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">nil</span>]];</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
但这样的设计存在两个严重的坏问道:
AndSpec
与OrSpec
存在明显的代码重复,OO
设计的第一个直觉就是通过抽取基类来消除重复。
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@interface</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">CombinableSpec</span> ()</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@property</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">nonatomic</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">strong</span>) <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">NSArray</span> *specs; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@implementation</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">CombinableSpec</span></span> + (instancetype)spec:(CombinableSpec *)spec, ... NS_REQUIRES_NIL_TERMINATION { va_list args; va_start( args, spec ); <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">NSMutableArray</span> *mArray = [@[spec] mutableCopy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> ( ;; ) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">id</span> tempSpec = va_arg( args, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">id</span> ); <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (tempSpec == <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">nil</span>) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>; [mArray addObject:tempSpec]; } va_end( args ); CombinableSpec *combinableSpec = [[CombinableSpec alloc] init]; combinableSpec<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.specs</span> = [mArray copy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> combinableSpec; } - (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span>)satisfy:(Product *)product { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (ProductSpec *spec in _specs) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ([spec satisfy:product] == _shortcut) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> _shortcut; } } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> !_shortcut; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li></ul>
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@implementation</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">AndSpec</span></span> - (instancetype)init { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span> = [<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span> init]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span>) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span><span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.shortcut</span> = <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">NO</span>; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span>; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@implementation</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">OrSpec</span></span> - (instancetype)init { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span> = [<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span> init]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span>) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span><span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.shortcut</span> = <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">YES</span>; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span>; } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>
- 大堆的
初始化方法
让人眼花缭乱
<code class="hljs ruby has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">[<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">findProducts:</span>_products <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">bySpec:</span>[<span class="hljs-constant" style="box-sizing: border-box;">NotSpec</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">spec:</span>[<span class="hljs-constant" style="box-sizing: border-box;">AndSpec</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">spec:</span>[<span class="hljs-constant" style="box-sizing: border-box;">ColorSpec</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">specWithColor:</span><span class="hljs-constant" style="box-sizing: border-box;">RED</span>], [<span class="hljs-constant" style="box-sizing: border-box;">BelowWeightSpec</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">specWithBelowWeight:</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>], <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">nil</span>]]];</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
Sixth Attempt: Using DSL
可以引入DSL改善程序的可读性,让代码更具表达力。
我们先添加一些DSL:
<code class="hljs perl has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">static ProductSpec <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*COLOR</span>(ProductColor color) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> [ColorSpec specWithColor:RED]; } static ProductSpec <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*BELOWWEIGHT</span>(float limit) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> [BelowWeightSpec specWithBelowWeight:limit]; } static ProductSpec <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*AND</span>(ProductSpec <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*spec1</span>, ProductSpec <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*spec2</span>) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> [AndSpec spec:spec1, spec2, nil]; } static ProductSpec <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*OR</span>(ProductSpec <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*spec1</span>, ProductSpec <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*spec2</span>) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> [OrSpec spec:spec1, spec2, nil]; } static ProductSpec <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*NOT</span>(ProductSpec <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*spec</span>) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> [NotSpec spec:spec]; }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li></ul>
这样我们的代码表现起来就是这样的
<code class="hljs oxygene has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">[<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span> findProducts:_products bySpec:<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">NOT</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">AND</span>(COLOR(RED), BELOWWEIGHT(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>)))];</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
Seventh Attempt: Using a Lambda Expression
可以使用Block改善设计,增强表达力。
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">- (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">NSArray</span> *)findProducts:(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">NSArray</span> *)products byBlock:(<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span> (^)())block { <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">NSMutableArray</span> *list = [@[] mutableCopy]; <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (Product *product in products) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (block(product)) { [list addObject:product]; } } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> list; }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>
代码现在开起来是这个样子
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">[<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span> findProducts:_products byBlock:^<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">id</span> p) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> [p color] == RED;}];</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
构造DSL,复用这些Block
<code class="hljs rsl has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">ProductSpecBlock <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">color</span>(ProductColor <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">color</span>) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> ^BOOL(id p) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> [p <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">color</span>] == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">color</span>;}; } ProductSpecBlock weightBelow(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> limit) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> ^BOOL(id p) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> [p weight] < limit;}; }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>
<code class="hljs erlang has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-pp" style="box-sizing: border-box;">- <span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(void)</span>test7_2 { [self findProducts:_products byBlock:color<span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(<span class="hljs-variable" style="box-sizing: border-box;">RED</span>)</span>]; }</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>
Eighth attempt: Using NSPredicate
还可以使用标准库
<code class="hljs ruby has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">[<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span>.products <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">filteredArrayUsingPredicate:</span>[<span class="hljs-constant" style="box-sizing: border-box;">NSPredicate</span> <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">predicateWithFormat:</span>@<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"weight > 10"</span>]];</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
结束
今天的编码就到此为止了, 这篇文章本是Horance所写, 笔者将用OC实现了一遍.如果咱们不是iOS Developer的话, 还是有其他attempt的, 如泛型
.
作者介绍
- 刘光聪,程序员,敏捷教练,开源软件爱好者,目前供职于中兴通讯无线研究院,具有多年大型遗留系统的重构经验,对面向对象,函数式,大数据等领域具有浓厚的兴趣。
- github: https://github.com/horance-liu
- email: horance@outlook.com
- 邢尧, 资深开发工程师, iOS Developer, 开源软件爱好者, 追求真理比占有真理更加难能可贵