今天看到《代码大全第二版》里面对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];
}
复制代码
改动点:
- 将按钮作为类的私有属性
- 将改变按钮对象的selected状态包装起来,提供更高层的抽象selectOn和selectOff接口,抽象成更像现实世界一样操作实体,选中单元格和取消选中单元格,而不是直接接触底层细节,将按钮对象的selected属性设置成YES或NO。
- 调用方调用更高层次、更具说明意义的抽象接口selectOn和selectOff方法,而无须关心实现细节。cell内部也可通过此方法改变外观,以后关于外观的改动也无须在多个地方改动了。
- 回调方法职责更单一,原来在一个方法中处理选中和非选中,现在分开到两个方法中,干掉了一个if,也就降低了函数的复杂度,以后添加选中或取消选中后可能会处理其它事情将更容易维护。
- 若以后将按钮对象更换为其它类型对象,或是更复杂的自定义按钮对象,那只需改动封装内的内部实现,不影响程序的其它地方的使用。
改进后的代码:
- 隐藏了实现细节,提高了类的封装性
- 类的接口和类保持一致的抽象层次
- 类接口更高层次的抽象
- 单一职责
有同学可能觉得这样的改动有那么必要吗,弄的如此大张旗鼓,但我们应该时刻保持代码的干净整洁,如果每个类都不能时刻保持对外接口的高度一致抽象层次,那么多个迭代以后,这样的类将越来越难以理解和难以维护,类持续的庞大和存在各种互不相关的数据和逻辑,增加新功能时更容易牵扯老功能的bug,我们应该时刻记住
勿以恶小而为之,勿以善小而不为
转载于:https://juejin.im/post/5cff4ecaf265da1b8d16136a