首页 > 代码库 > 组件接口(API)设计指南[3]-委托(delegate)和数据源协议(data-source protocols)
组件接口(API)设计指南[3]-委托(delegate)和数据源协议(data-source protocols)
*返回目录阅读其他章节: http://blog.csdn.net/cuibo1123/article/details/39894477
委托(delegate)和数据源协议(data-source protocols)
委托协议是一个非常好的设计,它能让你用简单灵活的方式去实现MVC模式,并能增强松散耦合以及养成良好的API设计习惯。
这里是MGTileMenu的委托协议。
我们几乎可以在任何组件中利用经典的委托(delegate)和数据源协议(data-source protocols)。如果你想显示一些数据,那么数据源协议可能就非常适合你,例如你有以下问题:
1:我有很多事物(有很多条数据)。
2:设置每个事物(数据)X的成员Y的值。
同样,在几乎所有情况下,一个经典的委托(delegate)协议形式如下:
1:这件事应当做吗(一个动作发生前,通常调用一个带有返回值的委托方法,决定动作是否执行)?
2:这件事将要做(在动作发生前,代理可以对动作做出响应)。
3:这件事做完了(在动作发生后做出响应)。
这也被称为Should(应该),Will(将要), Did(完成)协议。它把Will-Did联系在了一起,相比通知(notification),这样更加紧密。
我下面提及的内容可能会引发争议:我觉得委托(delegate)和数据源(data-source)完全可以合并成一个协议。我在MGTileMenu中的一些地方这样做了。
我完全接受将它们分开的原则,我能想到很多要将它们分开的情况。苹果公司的接口一般也将它们分开。这很好。
不过,根据我的经验,在大多数情况下,将它们合并起来也很好。大多数人在同一个位置处理数据源(data-source)和委托(delegate)。我从来没见过有人提出要统一这两个协议的建议,但是我也很少记得需要在不同位置处理这两个协议的情况。
如果你关心代码整洁性,还是有必要分开处理数据源(data-source)和委托(delegate)。我只是认为你不必过分担心把它们结合起来有什么不好。
规则13: 限制“@required”委托方法
在设计委托方法时,必须格外小心。太多required(必须)委托方法往往显得:
1:容易造成糟糕的违约行为。
2:在代码中有太多你自己的意愿。
一个良好的设计应该具有非常非常少的委托方法-只是最低限度的实现功能。请谨慎选择。同时,请记住,在以后很容易添加可选(optional)方法,但很难把可选(optional)方法转换成必须(required)方法(用户会抱怨,这是理所当然的)。
MGTileMenu有五个必须方法,其中四个是数据源方法:
//总数(最多能显示5页)
- (NSInteger)numberOfTilesInMenu:(MGTileMenuController *)tileMenu;
// 瓦片编号从0开始
- (UIImage *)imageForTile:(NSInteger)tileNumberinMenu:(MGTileMenuController *)tileMenu;
// 瓦片编号从0开始
- (NSString *)labelForTile:(NSInteger)tileNumberinMenu:(MGTileMenuController *)tileMenu;
// 瓦片编号从0开始
- (NSString *)descriptionForTile:(NSInteger)tileNumberinMenu:(MGTileMenuController *)tileMenu;
前两个是基本的数据源协议。第三个和第四个做更多的事情,但也强迫用户必须提供更多条件,接受你的的意愿:我强迫你提供标签,在为每一个标签提供描述,我觉得这样会让你比较满意。
还有一个委托方法:
// 瓦片编号从0开始
- (void)tileMenu:(MGTileMenuController *)tileMenudidActivateTile:(NSInteger)tileNumber;
这也是一个必须的方法,它可以让你发现瓦片被激活。如果你不关注这个动作,那么你也就没必要使用MGTileMenu了,所以,它是必须(required)的。
规则14: 无障碍设计
这条规则是:让事情变得更易用(针对障碍人群的易用)。不要在最后一刻考虑这个问题,从一开始就进行无障碍设计。如果你遵循“在你的控件中使用控件”原则,你可能不需要特别的工作就已经具有了这样的功能。
委托(或者更确切的说,是数据源)方法的一个伟大之处,就是可以为其他开发人员提供VoiceOver(视觉辅助)功能。如果你能重新规划视觉辅助的东西(如显示文字的标签)作为VoiceOver配音的标签,那就更好了(同样,在大多数情况下VoiceOver已经为你处理好了)。
从社会意识的角度来说,让人很难选择不去支持无障碍设计。我推荐你阅读一下苹果公司关于视觉辅助编程的建议,我也写过一篇关于在应用中支持VoiceOver的文章,你可能会有兴趣。
规则15: 使用具有语义的参数
这条不仅仅适用于协议,但在协议中这点尤为重要。即使在工作中会给你带来更多的麻烦、更加繁琐,也仍然应该使用语义上合适的对象存储数据。
比如要存储某个指定的日期,不要使用数字,而是使用NSDate对象。如果当前有结构或对象更适合描述一个数据,你就应该使用它们。如果没有,你就需要创建一个对象(在需要的情况下)。
当然,有一些标准的例外,比如一纬索引,没有理由为它们封装一个语义对象,因为对于一个数字来说,没有什么比NSNumber更适合的语义描述,可以在绑定/解除新的描述成员(在类中增加新的描述成员)上提供更多的方便。
第16条: 如果语义不合适就增强API
上一条中,使用具有语义的参数这个方法,你能够新定义的对象基本上都是逻辑上已经存在的(通常,它是逻辑上已经存在的结构,你只是实现它)。
无论你多聪明,语义的特征总是相似的。而扩展一个新的API以使语义切合,也是一个好的选择。例如:
一个联系人列表的每个表格有一个联系人-的相关API。
一个日历中以月视图网格查看哪天有约会-的相关API。
不要强迫自己(或其他开发人员)不断抽象API以符合语义,可以制作能体现组件实际用途的新API来代替这个方法。
例如MGTileMenu’s的委托协议并不是把菜单当作UIButtons的集合,而是作为一个统一的菜单和一些有对应的编号以及自己的显示属性的条目。
规则17: 高亮是值得关注的
了解到这一点后,我不得不回到我认为完成了的API中添加新的委托(delegate)方法和通知(notifications)。对于交互式控制,高亮值得关注,这里的“值得关注”,指的是对使用者的应用程序存在的意义。
任何控制完全被触发时,都将会通知(在某些时候可能只是调用了动作方法)给应用程序。但是当它们在视觉上产生变化(选择、按下等)而没有被触发时(高亮显示),则比较少会通知给调用方。事实证明,这实际上是非常重要的。应用程序可能需要:
1:添加、删除或重定义辅助UI。
2:更新显示方式。
3:提供一些上下文帮助。
4:其他一些你无法预见的事情。
高亮虽然是一组可选的委托方法,但它们应该存在。而且实现它们总是比较容易。
//tileNumber从0开始
- (void)tileMenu:(MGTileMenuController *)tileMenudidSelectTile:(NSInteger)tileNumber;
//tileNumber从0开始
- (void)tileMenu:(MGTileMenuController *)tileMenudidDeselectTile:(NSInteger)tileNumber;
规则18:可选方法不是一个承诺
我们的许多可选委托方法都是一种非此即彼的情况:要么重新实现他们,要么使用默认的行为。如果你重新实现了委托,你就要完全负责他的行为,这并不是理想的选择。
在一个委托方法被实现,但是并不返回正确的行为时,你还是应该执行默认行为。这听起来很明显,但令人惊讶的是许多组件会轻率的信任代理方法返回的任何类型,而不进行检查,只是因为委托声明承诺(定义)了实现时应该返回的类型。
特别是在定制组件视觉样式的时候,比如背景颜色或图像。你应该非常非常小心的在某些情况下进行干预,让他依然显示你的默认外观。比如:难道用户真的想要什么都不显示?这种行为有没有意义?是否会使控件看起来像是出了故障?如果是这样,就可以像委托方法从未被实现一样执行默认行为。
对此有一个标准的方式:通过返回类似nil的值来故意调用默认方法。
在MGTileMenu中,你可以通过实现三个委托中的任意一个来自定义背景,让背景使用纯色、图片或渐变色填充。你也可以让代理方法返回nil,以便执行默认的行为。
如果你想让背景完全透明,那么你必须明确的返回一个透明对象(clearColor,或者一个透明的UIImage)。
规则19: 是谁在说话
这是一个简单的规则,也是一个简单的错误。在你的委托方法中,应该总是用发送方(调用对象)作为参数。即使是单例也应如此。因为你无法知道用户是否会同时使用两个实例。
这样:
- (void)tileMenu:(MGTileMenuController *)tileMenudidActivateTile:(NSInteger)tileNumber;
而不是这样:
- (void)tileMenuDidActivateTile:(NSInteger)tileNumber;// 嗯,哪个菜单?
规则20: 查询方法中显著的参数放在第一位
一个真正的数据源协议一定要有一个查询方法,用于查询的标志(例如索引)应该放在方法的第一个参数中。
例如:
- (UIImage *)imageForTile:(NSInteger)tileNumberinMenu:(MGTileMenuController *)tileMenu;
而不是:
- (UIImage *)tileMenu:(MGTileMenuController *)tileMenuimageForTile:(NSInteger)tileNumber;
返回的类型应该自然的成为方法名的第一部分,以减少意外。数据源协议往往有许多类似的命名方法,把特别的地方放在方法的最开始,更便于阅读,也更容易让编辑器自动补全(只要记住返回类型就行了)。
有人指出,苹果的UITableViewDataSource协议不是这样做的,而是把发送对方放在了第一的位置,例如:
- (NSInteger)tableView:(UITableView *)tableViewnumberOfRowsInSection:(NSInteger)section;
我想说:我知道这个情况,但是我坚持我的观点。
规则21: 将发送对象放在通知方法的第一位
一个不是用于查询,而是用于通知的委托协议,应该把消息发送者放在第一位(继规则19:是谁在说话)。
- (void)tileMenu:(MGTileMenuController *)tileMenuwillSwitchToPage:(NSInteger)pageNumber;
这就像有两个人在互动之前有一个对话,你不会只是站起来说:“一个人要迟到了”,因为这样其他人就不得不问:“是谁?”
相反,在开始之前说明谁在说话。这是一个惯例,并能够轻而易举的用于区别通知(delegate)和查询(data-source)。
规则22: 如果一个惯例被打破,就忽略它
请记住上面所说的所有规则,但是是否要遵守这些规则,则需要根据你的情况在优越性上进行判断。如果一个约定被打破,就忽略它,不用担心(如果你的设计更优越)。
例如:有一个预先存在的菜单控件,现在可以通过名称为validateMenuItem:的委托方法启用或禁用菜单项。为了统一起见,我很想用同样的方法名作为我所有委托方法的一部分,所以我决定不在用这个名称,因为:
1:它有一个可怕的名字,“Validate”?而不是“enable”。
2:这个方法在我的模块中是必须的。
3:它打破了我的其他委托的命名方案。
相反,如果我用如下方法命名,则更简单也更容易理解:
- (BOOL)isTileEnabled:(NSInteger)tileNumberinMenu:(MGTileMenuController *)tileMenu;
具体的措辞并不重要,如果你有一个方法让你更容易知道它是什么以及如何使用它。那它就是更好的。
阅读下一章节: http://blog.csdn.net/cuibo1123/article/details/39894477
-------------------------
英文原名《API Design》
作者:Matt Gemmell
原名链接:http://mattgemmell.com/api-design/
中文版由xoneday翻译
欢迎访问译者博客:http://blog.xoneday.com
新浪微博:@xoneday某天
如果这片译文给您带来了帮助,希望您能通过下载我的APP来支持我:
豆瓣读书:https://itunes.apple.com/cn/app/id695492935
便签夹:https://itunes.apple.com/cn/app/id580552733
便签夹 豆瓣读书
*转载声明:请勿删减作者/译者信息与支持部分的内容,如不接受此条款请勿转载。
组件接口(API)设计指南[3]-委托(delegate)和数据源协议(data-source protocols)