iOS開發:八次嘗試 帶你走進精益編程

來源:互聯網
上載者:User

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的實現與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]];

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

  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的, 如泛型.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.