iOS开发:八次尝试 带你走进精益编程

[复制链接]
查看: 17|回复: 1
  • TA的每日心情
    慵懒
    2018-4-28 09:37
  • 1459

    主题

    1479

    帖子

    11万

    积分

    管理员

    小语

    Rank: 9Rank: 9Rank: 9

    积分
    110351
    发表于 2018-4-2 10:57:19 | 显示全部楼层 |阅读模式

    开场

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

      Product Repository: Filtering Operation

      Code start

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

      第一个需求并不复杂.

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

      First Attempt: Hard Code

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

      - (NSArray *)findAllRedProductsNSArray *)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 *)findAllGreenProductsNSArray *)products

      {

      NSMutableArray *list = [@[] mutableCopy];

      for (Product *product in products) {

      if (product.color == GREEN) {

      [list addObject:product];

      }

      }

      return list;

      }

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

      - (NSArray *)findProductsNSArray *)products byColorProductColor)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 *)findProductsNSArray *)products byWeithfloat)weight

      {

      NSMutableArray *list = [@[] mutableCopy];

      for (Product *product in products) {

      if (product.weight < weight) {

      [list addObject:product];

      }

      }

      return list;

      }

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

      - (NSArray *)findProductsNSArray *)products byColorProductColor)color byWeithfloat)weight typeint)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的, 如泛型.






    该用户从未签到

    0

    主题

    16

    帖子

    147

    积分

    注册会员

    Rank: 6Rank: 6

    积分
    147
    发表于 2018-4-30 00:08:41 | 显示全部楼层
    我只是路过打酱油的
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    精选推荐

    在线客服
    热线电话

    微信公众账号
    返回顶部 关注微信 下载APP 返回列表