你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 8 次嘗試,帶你走進 iOS 精益編程

8 次嘗試,帶你走進 iOS 精益編程

編輯:IOS開發基礎

0.jpg

作者:曉月 授權本站轉載。

開場

今天, 我們將從一個小功能開始, 先去不假思索的實現它

  • 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的實現與ColorSpec,BelowWeightSpec之間存在明顯的重復

此刻,需要尋找更本質的抽象來表達設計,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]];

1

但這樣的設計存在兩個嚴重的壞問道:

  • AndSpec與OrSpec存在明顯的代碼重復,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 = [[CombinableSpec alloc] init];
    combinableSpec.specs = [mArray copy];
    return combinableSpec;
}
- (BOOL)satisfy:(Product *)product
{
    for (ProductSpec *spec in _specs) {
        if ([spec satisfy:product] == _shortcut) {
            return _shortcut;
        }
    }
    return !_shortcut;
}
@end
@implementation AndSpec
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.shortcut = NO;
    }
    return self;
}
@end
@implementation OrSpec
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.shortcut = YES;
    }
    return self;
}
@end
  • 大堆的初始化方法讓人眼花缭亂

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

Sixth Attempt: Using DSL

可以引入DSL改善程序的可讀性,讓代碼更具表達力。

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

結束

今天的編碼就到此為止了, 這篇文章本是Horance所寫, 筆者將用OC實現了一遍.如果咱們不是iOS Developer的話, 還是有其他attempt的, 如泛型.

作者介紹

  • 劉光聰,程序員,敏捷教練,開源軟件愛好者,目前供職於中興通訊無線研究院,具有多年大型遺留系統的重構經驗,對面向對象,函數式,大數據等領域具有濃厚的興趣。 

  • 邢堯, 資深開發工程師, iOS Developer, 開源軟件愛好者, 追求真理比占有真理更加難能可貴 

    • Github: https://github.com/uxyheaven

    • Blog: http://blog.csdn.bet/uxyheaven


  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved