iOS代码重构之提高类的接口抽象能力

1,335 阅读5分钟

今天看到《代码大全第二版》里面对ADT及类抽象能力的描述让我受益匪浅,查看我们的iOS项目,马上就找到了这样的坏味道(不良好的类接口设计),下面就分享一下我的改进方法。(这里的类名做了修改,并非实际项目中的类名)


改进前:

这是一个自定义单元格类:

@protocol  CustomCellDeletage <NSObject>

- (void)checkDidTouch:(CustomCell *)cell;

@end

@interface CustomCell : UITableViewCell

@property (nonatomic, weak)id<CustomCellDeletage> delegate;

@property (nonatomic, retain)UIButton * checkBtn;
//这里直接暴露了类的成员变量细节,导致类对自身内部控制的松动,按钮的状态改变将难以追踪,并且这个类和按钮对象如何使用也会让调用方摸不着头脑,这次的改动将以此为重

@end

@implementation CustomCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.selectionStyle = UITableViewCellSelectionStyleNone;
        _checkBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        [_checkBtn addTarget:self action:@selector(checkBtnPressed:) forControlEvents:UIControlEventTouchUpInside];
        //...省略部分代码

    }
    return self;
}
- (void)checkBtnPressed:(id)sender {
    // 切换选中状态
    _checkBtn.selected = !_checkBtn.selected;
    
    if (self.delegate && [self.delegate respondsToSelector:@selector(checkDidTouch:)]) {
        [self.delegate checkDidTouch:self];
    }
}

外部Controller调用:

cell.checkBtn.selected = [self isChecked:orderDict[@"orderId"]];

外部Controller回调:

#pragma mark  CustomCellDeletage ---- 点击选择按钮事件
- (void)checkDidTouch:(CustomCell *)cell {
    // 如果是选中状态,就添加   反之这移除
    if (cell.checkBtn.selected) {
        [self.checkArray addObject:cell.orderDict];
    } else {
        [self.checkArray removeObject:cell.orderDict];
    }
    [self updateViewInfo];
}

这里根据《代码大全第二版》作者书里对ADT好处的描述来做代码评价。由于这里直接暴露了类的成员变量checkBtn而带来了很多坏处。
ADT(抽象数据类型)
  • 隐藏实现细节

如果数据类型发生改变,你只需在一处修改而不会影响程序的其余部分。例如这里如果由UIButton类型变为UISwitch,应只需要改动接口的具体实现,而不影响接口调用方

  • 让接口能提供更多信息,程序更具自我说明性

例如这里后面改进了类的接口为selectOn和selectOff一组更具说明意义的方法

抽象
  • 类的接口为隐藏在其后的具体实现提供一种抽象
  • 类的接口应该展示一致的抽象层次

例如这里cell单元格类,应提供选中单元格和取消选中单元格的接口,而不是直接暴露成员变量

  • 尽可能地限制类和成员的可访问性以提高封装性
  • 不要公开暴露成员数据
  • 避免把私用的实现细节放入类的接口中

核心改动思想

把对按钮的操纵隔离到一组子程序里,为需要操作按钮的其它程序部分提供更好的抽象层,同时可以在针对按钮属性状态的操作发生变化时提供一层保护。 像在现实世界中那样操作实体,而不用在底层实现上操作它


改进

@protocol  CustomCellDeletage <NSObject>

- (void)customCellDidSelectOn:(CustomCell *)cell;

- (void)customCellDidSelectOff:(CustomCell *)cell;

@end

@interface CustomCell : UITableViewCell

@property (nonatomic, weak)id<CustomCellDeletage> delegate;

- (void)selectOn;

- (void)selectOff;

@end

@interface CustomCell ()

@property (nonatomic, strong)UIButton * checkBtn;

@end

@implementation CustomCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.selectionStyle = UITableViewCellSelectionStyleNone;
        _checkBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        [_checkBtn addTarget:self action:@selector(checkBtnPressed:) forControlEvents:UIControlEventTouchUpInside];
         //...省略部分代码
    }
    return self;
}
- (void)checkBtnPressed:(id)sender {
    if (_checkBtn.selected) {
        [self selectOff];
        if (self.delegate && [self.delegate respondsToSelector:@selector(customCellDidSelectOff:)]) {
        [self.delegate customCellDidSelectOff:self];
    }
    } else {
        [self selectOn];
        if (self.delegate && [self.delegate respondsToSelector:@selector(customCellDidSelectOn:)]) {
        [self.delegate customCellDidSelectOn:self];
    }
    }
}
- (void)selectOn {
    _checkBtn.selected = YES;
}
- (void)selectOff {
    _checkBtn.selected = NO;
}
@end

外部Controller调用:

if ([self isChecked:orderDict[@"orderId"]]) {
        [cell selectOn];
    } else {
        [cell selectOff];
    }

外部Controller回调:

#pragma mark <CustomCellDelegate>
- (void)customCellDidSelectOn:(CustomCell *)cell {
    
    [self.checkArray addObject:cell.orderDict];
    [self updateViewInfo];

}

- (void)customCellDidSelectOff:(CustomCell *)cell {
    
    [self.checkArray removeObject:cell.orderDict];
    [self updateViewInfo];
}

改动点:

  1. 将按钮作为类的私有属性
  2. 将改变按钮对象的selected状态包装起来,提供更高层的抽象selectOn和selectOff接口,抽象成更像现实世界一样操作实体,选中单元格和取消选中单元格,而不是直接接触底层细节,将按钮对象的selected属性设置成YES或NO。
  3. 调用方调用更高层次、更具说明意义的抽象接口selectOn和selectOff方法,而无须关心实现细节。cell内部也可通过此方法改变外观,以后关于外观的改动也无须在多个地方改动了。
  4. 回调方法职责更单一,原来在一个方法中处理选中和非选中,现在分开到两个方法中,干掉了一个if,也就降低了函数的复杂度,以后添加选中或取消选中后可能会处理其它事情将更容易维护。
  5. 若以后将按钮对象更换为其它类型对象,或是更复杂的自定义按钮对象,那只需改动封装内的内部实现,不影响程序的其它地方的使用。

改进后的代码:

  • 隐藏了实现细节,提高了类的封装性
  • 类的接口和类保持一致的抽象层次
  • 类接口更高层次的抽象
  • 单一职责

有同学可能觉得这样的改动有那么必要吗,弄的如此大张旗鼓,但我们应该时刻保持代码的干净整洁,如果每个类都不能时刻保持对外接口的高度一致抽象层次,那么多个迭代以后,这样的类将越来越难以理解和难以维护,类持续的庞大和存在各种互不相关的数据和逻辑,增加新功能时更容易牵扯老功能的bug,我们应该时刻记住

勿以恶小而为之,勿以善小而不为