转自oschina 参与翻译(14人):git
我最常作的开发任务是设计一个可重用的API组件。组件一般为iOS(尽管有时它们是OS X) 设计的,且老是GUI控件或某种视图。github 多年来,我为客户开发了不少API组件,其中包括像Apple这样的客户,并且我已经很了解这个过程。我也按期发布开源组件,而且我把曾经对我有帮助的资料和API设计指南放在一块儿与你们分享。api 这是一个重要的主题,不管你是一个开源贡献者,或做为团队的一员参与开发大型的应用,或者只是设计本身的软件。正如开发一个应用的过程,API接口是使用你代码的开发者对你代码的第一印象,将严重影响着开发者决定是使用或扔掉它。架构 APIs是开发者的用户体验。我一直惊讶,具体到这个流行平台上没有不少的资料是写咱们这方面工做的。app 当咱们阅读一些设计指南时,必要的时候,我将要用我最近发布的开源GUI组件MGTileMenu做为一个例子。你能够在这里先阅读全部关于MGTileMenu的信息,若是你喜欢。框架 |
![]() hyaicc
|
其它翻译版本(1) |
如何使人满意应用程序接口(API)设计和用户界面、用户体验设计很相像。你的目标用户有不一样的需求和特色,但归根结底他们的目标仍是把需求完成而已。就像一个设计友好、易用的应用程序的用户界面同样,你须要让你的API有如下的特色:函数
如同人们设计的其它的软件同样,咱们首先须要考虑的是使用案列。咱们的设计须要使最常常被用到的的功能简单易用,不须要过分的配置。在默认配置下软件就应该是可用的,而且具备必定的可配置性。软件的设计应该具备可探索性,并且应该容许用户从已知的的范例中推广到其余应用场景。这和咱们建立一个用户界面的规则很是的相像。工具 |
![]() WangWenjing
|
其它翻译版本(1) |
开发者的界面用于和开发者交互的元素使用四个主要的显示意味着:
咱们须要把每个都设计成:明智和慎重的,用于人类使用。这里有2个问题当你设计API的时候须要考虑:
咱们的核心原则是让已有的类和模型保持一致性,以用来保证咱们能够把一个开发者不熟悉的控制让他很轻松的在他能够理解的平台上使用。使用标准APIs,模型,和模式不管是否是可能(而且这个应该是总用的)。对于终端用户,熟悉和直觉性是和代码层级同样重要的。 让咱们看看咱们以前提到的这四个元素: |
![]() 周荣冰
|
其它翻译版本(1) |
类接口Here’s the interface file for MGTileMenu. 在咱们讨论具体的接口以前,这有一些涵盖范围比较普遍的规则: Rule 1: 使用方言我所看到最多见的错误是API的设计利用了外来的约定。APIs 属于固定平台和固定的开发者生态系统。你根本没法使用任何习语和你用过的其余平台的架构,这样作会污染您当前的代码库,并对其余开发人员的效率形成损害。 在coding以前要了解你目标平台的约定,好比,在iOS 或者 OS X,不使用异常对待control的流程 。以适当的方式命名你的方法(一般指有足够详细,但也应该有足够的简洁)。 了解协议,和委托,类别分别是什么。在你的代码中使用他们。学习相关的构造函数和析构函数的命名方案。请遵照内存管理规则。词汇和语法是不可分割的,你要么发展为一个固定的的平台,或者你跨平台。 |
![]() 魏涛
|
Rule 2: 设计解耦任何component的设计应该没有链接到你当前建立的项目,若是他是一个GUI control或者一个视图,它应该默认显示一些东西。使用现有的框架做为一个指南,与委托协议,精心设计的/命名的API方法和通知在适当的地方保持松耦合。 一个很明显的,但很是有效的方式,是每次为你的component建立一个项目,并逐渐的隔离开发component。强迫本身使用本身的API。远离无关的类。 接下来,让咱们来适当谈谈类的接口。初始化方法的接口中最重要的部分之一,由于他们是人们如何开始使用您的组件。你的类将有必定的初始配置所需的设置。因此,一个明显的规律: |
![]() 魏涛
|
其它翻译版本(1) |
Rule 3: 必须设置初始化参数若是有什么须要设置的,不要等待 -须要它了就去作,若是你没有获得的东西的当即返回nil。
Rule 4: 容许访问初始化参数Allow access to initializer parameters这个前一个结果的必然结果: 记住不要仅仅传入参数,应该能够经过属性或者赋值来访问他们,若是他们能够经过任何方式来一场“按摩”(修改,重写等)
|
![]() 魏涛
|
||||
其它翻译版本(1) |
Rule 5: 注释你的header文件 (包含默认值)实际上,你不总为component提供单独的文档。若是你不提供文档,你的.h文件(包括demo app)就是你的文档。他们应该适当的描述,个人意思是:
特别是,你应该简要注释在属性或访问器旁边;头文件扫描比在初始化实例的时候更容易。
|
![]() 魏涛
|
||||||
其它翻译版本(1) |
Rule 6: 习惯于运行3行代码你的类应该设计成只须要最少的代码来集成(包括后续将用到的委托/数据源协议)。但不包括委托方法,你应该着手于用3行代码就能够达到测试的目的。 这3行代码以下:
|
![]() haoio
|
||||||||||||||||
其它翻译版本(1) |
Rule 7: 臃肿的demo一般意味着组件是糟糕的另外一个推论:您的demo的大小是衡量你component质量的标准,其值越小越好。Demo/Code 应该尽量的小巧而又精简(用于演示,旨在描述全部组件的定制或功能)。 核心思想是当你的代码从你的空的Xcode项目模板到你的demo中应该保持最小化的修改。这并非一个好的借口当你须要复制粘贴demo来让你的component运行。 |
![]() 魏涛
|
Rule 8:分析特定的场景我对于apps的准则就是:不要让用户去作选择。选择知足多数人的人性化的默认设置,略去参数设置窗口。毕竟,好的软件都是有倾向性的。 因为运用场景不是那么的清晰明确,因此不一样的组件面对的状况也有些不一样。你固然能够作一个只知足某种特定状况的组件,可是,一般咱们都但愿有些灵活性。你毫不会准确的知道另外一个开发者将会怎样使用你的组件,因此你必须作到有必定的通用性。 认真的选择你的定制点是很重要的。考虑依赖关系更加的重要——不是对编译/连接的理解,而是定制类型之间的逻辑关系。个人方法就是尽可能从“方面”的层次上考虑而不是实例变量的层次上。你但愿你的组件的那些方面容许被定制化?那么你就知道哪些特定的属性须要暴露。 经过不暴露足够的的配置点,就能够很容易的弱化某个特定的定制类型。例如:1.若是没有考虑圆角半径,就不要暴露宽度和高度。 2.若是没有高亮的背景颜色,就不要暴露背景颜色。 3.若是没有空间,就不要暴露大小。 具体的状况取决于具体的组件,可是须要从外观或者功能角度来考虑属性之间的关系。学会理解开发者。不要禁止组件的个性化,让它灵活些。
|
![]() crAzyli0n
|
Rule 9: 多点属性,少点方法有一个特定的模式持续出如今我所喜欢的一些来自标准库、开源的第三方以及我本身的一些代码组件。它是一个组件中属性(或者访问器,定制点)个数与方法(也就是全部其它的,从初始化到状态更新)个数的比率。 多属性少方法(再申明一次,方法不是指在Interface Builder中的那些)。MGTileMenu有一个初始化函数和四个实际上供公共使用的愿意很是(每个都很方便调用另外一个方法)。对定制点而言,它的比率有4倍之多。我认为这是一个很是好的比率,使组件不但在功能上变得简洁,并且在定制时更加灵活。
|
![]() showme
|
Rule 10: 在你的控件中使用控件 一个同时简化组件API和实现的好方法就是在你的实现中使用己有的控件。具备统一的外在并不意味着你不可使用已经存在的组件(确实,这是软件工程当中的一个基本原则)。 考虑是什么让UITableViewCell和UIButton拥有简单的API接口,发现这是由于它们使用诸如UIImageView和UILabel这样的子控件。你也能够,而且该这样去作,而且若是可行的话使用相应的子控件来使你的类接口保持简单不变。 举个例子,在MGTileMenu中,它的外表是常规的UIButtons(不仅是子类)。跟在一个的view中自定义去画它的样式、处理输入事件和支持访问而言,极大地简化了它的实现。 |
![]() showme
|
Rule 11: 于人方便就是于己方便你会很天然地在实现的过程当中加入合适的方法并下意识地将其设置为私有的。相反,应该考虑是否能够公开这些方法,使这些组件能被集成到别人的应用程序。 对你而言那些如何简单方便地加入一个方法或函数的方式,对开发者而言一样如此。 举例来讲,在MGTileMenu中,我建立了这些合适的函数:
|
![]() showme
|
Rule 12:魅力能够,魔数却不行。你早晚都会在你的component中加入一些魅力。人人都但愿有大量的Steve Jobs式的直观、宜人、富于掌控力的魅力,可是我所说的倒是代码中的一些东西,诸如拥有特殊含义的数字或者值。例如,-1在某项设置或者某个特殊场景中,就有着某个特定的意义。 很好,那样作真的挺好的。不爽的是你的代码中充满一些莫名其妙的原始值,更不爽的是还将它们暴露在API中。若是你正在暴露一些魔数,那么为了便于使用,最好仍是用#define或者常量或者其余的东西包装一下它们,这样会让魔数更加的直观和易于理解。
|
![]() crAzyli0n
|
代理和数据源协议代理协议是奇妙的。它们是实现MVC模式的简单的、熟悉的和灵活的方式,它们更会使你养成松耦合的好习惯而且教你明智的API设计。 这个是 MGTileMenu’s delegate protocol. 有太多经典的代理和数据源协议供咱们用到几乎全部的组件中。若是你正在显示数据,这个准确的数据源协议可能更接近于:
一样的,在几乎任何的状况中,这个准确代理协议可能采用下面的形式:
这也被称为Should, Will, Did协议模式,这也巧妙的连结了以后的Will-Did通知模式。 让咱们来讨论一下你可能会以为有争议的话题:我发现将代理协议合并到数据源协议中被完美的接受了(也就是说将他们整合到一个协议中)。我在MGTileMenu和几个其余的组件中这样作了。 我彻底接受分离他们的原则,而且我能想到不少你想保持他们分离的例子。一般,苹果也是保持他们分离的。那好吧。 但是,就个人经验看,在大多数例子中合并他们是很好的。大多数人将数据源方法和代理方法放到同一个地方。我历来没有由于合并这些协议而抱怨过,我几乎不能记得一个存在分开的协议在不一样的地方使用的状况。 若是你重视清晰,或者有将代理从数据源中分离出来的需求,那么很明显你应该那样作。我只是不认为若是你合并他们会以为很差。 |
![]() 李远超
|
规则 13:限制‘required’代理方法的数量 当你选择设置哪些代理方法为required(必须实现)时必定要当心。太多的required方法将会代表:
一个精心设计的组件应该有不多不多required代理方法-仅仅是那些不得不的方法。当心选择。一样的,记住再后来添加optional(可选择实现)方法是很简单的,可是添加required方法就很是的困难(不少时候人们会抱怨)。 在MGTileMenu中有5个required方法,其中有4个是数据源方法:
前两个属于一个真正的数据源协议。第三第四个也是,可是这也代表了个人观点:我认为软件应该是容易理解的,而且我正在强制你为每个tile提供一个标签和表述来共读者们阅读。我很享受这些。 固然也有一个代理方法
这个方法是required,由于他告诉你如何来发现一个tile是激活状态的。若是你不打算实现这个方法,MGTileMenu将不会作任何有用的事情,甚至你可能就根本不能用它。因此他是必须实现的。 |
![]() 李远超
|
规则 14:设计成容易理解的 马上跟上这最后的规则:使设计是容易理解的。不要在最后才注意到它,偏偏相反,应该是在起初就设计成容易理解的。若是你遵循了“在你的控件中运用控件”原则,那么你可能在不知不觉中就遵循了这个原则。 上面展现的代理(倒不如说是数据源)方法是另外一个开发者为了使他们给语音辅助提供一些东西的地方。 若是你可以在视觉上(就像展现一个文本标签)自动地改变一些事情的意图就像一个语音辅助标签那样,那该多好啊(此外,在大多数例子当中语音辅助已经为你实现了这个)。 在交流方面保持清醒。作到不容易理解的设计却是困难的。我也写了另一篇关于在IOS应用中支持语音辅助功能的文章,这也是苹果向在容易理解的程序有联系的合伙人们推荐的。我也推荐它,可是我写它就是为了你能够接受它。 |
![]() 李远超
|
规则 15:利用语义对象来作参数 这不只仅适用于协议,但协议是尤为重要的部分。最好是在实际应用中,用合适的语义对象做为数据,虽然在你的实现中这样作可能会更加的麻烦。 若是你须要一个日期,不要用一些数字-而应该是一个真实的日期对象。对象或者结构体能够表示每同样事物,而且你应该有意地去用它们。若是须要的话能够建立一个类(你可能不会须要)。 固然一个标准的目录除外-除了基元没有任何理由能够把他们变成任何事物,自从NSNumber的加入,对于抵消打包/非打包带来的麻烦没有任何事物是语义地足够重要的。 |
![]() 李远超
|
规则 16:若是语义不合适的话就提升API 我时刻都在注意这一点。我曾在早些时候提到过,你怎么能就像一些事物已经存在(一般,就像是已经存在了的在你的实现中无心间用到的东西)了那样考虑几乎任何新的定制的控件呢? 那很是好,而且你是很聪明的,可是不能让语义赛过类似点。为了使语义合适,在一个已经存在了的API上叠加一个新的API绝对是好的(或者漂亮的)。例如:
诸如此类的等等。不要时常地强制你本身(或者其余的开发者)在抽象的实现API和真实的组件语义之间作精神上的转变 - 反而应该让这个API反映出组件真实的目的。 MGTileMenu 的代理协议经过不把菜单当作UIBUttons(实现了的)的集合而宁肯说是菜单的统一作到了这一点,利用每一个菜单中相关的有限的tile来展现内容。 |
![]() 李远超
|
Rule 17: 高亮是有趣的当我不得不回过头来添加一个新的代理方法和通知到我认为已经完成了的API中,我才意识到这一点。对交互控件来讲,高亮是有趣的。经过“高亮”,意味着其在应用程序中潜在的重要性。 任何控件都会通知App(在某种意义上来讲,可能只是经过调用一个动做或方法),当它被彻底触发时;可是当它们被高亮(选中,按住)或取消高亮被触发时,只有比较少的状况才会通知。这说明它实际上很是重要。应用程序可能须要:
|
![]() showme
|
Rule 18: 可选的方法不是一个保证咱们大部分人都把可选的委托方法当作二选一的情形:若是你不实现他们,就使用默认的行为;若是你要实现他们,那你就要为将发生的事情所有负责。那不是理想的事情。 在任何的提供一个可选的委托方法的实现中,你应该仍然返回去默认的行为,即便这个方法已经被实现了,但不要返回一些明显的东西。这听起来很明显,可是使人惊讶到底有多少组件无忧无虑地而后委托对象返回任何类型没有通过精细检查的愚蠢东西,仅仅是由于这委托莫名奇妙地答应经过实现这个方法来管理本身的行为。 我要具体地讨论下可视化的定制,如背景颜色和图片。很是很是仔细地考虑下,你是否不该该去干涉那种状况,同时依靠于你的默认界面。他们真的不想展现些东西吗?甚至让感受好一点?它会让控制看起来很糟糕吗?若是如此,插手介入吧,同时仅在若是委托方法历来不在开始的地方就实现的时候准备默认的。 相关地,用一个有文档的、标准的及不是很突兀的方式去慎重地从每一个可选的委托方法中经过返回一些如空的东西来执行默认的行为。 例如,MGTileMenu有一个比较复杂的层次方式让你能够自定义标题的背景。你能够实现三个委托方法中的任何(或者所有,或者不)方法去为每一个标题提供一个背景图片、梯度或者颜色。你也可以在任什么时候候为任何标题选择默认的行为,经过返回空或者NULL等适当的类型。 你将不得不尝试至关困难地去使标题的背景透明(经过返回清澈的颜色或者使用空的UIImage对象)。 |
![]() throwable
|
Rule 20: 在查询方法中首先放置可区分的参数一个真正的数据源协议总有将最感兴趣东西放在前边的方法。好比,你请求的指定质量或属性等。像这样:
不要像这样:
返回类型天然而然应做为方法名的第一部分,这并不会另人奇怪(请注意上边两个函数的不一样处)。数据源协议中常常有许多命名类似的方法,所以咱们应该最早考虑保持函数的惟一性和感兴趣部分。这样的话,(这些方法)更容易读,更容易作到自动补全。 有人指出:Apple公司的UITableViewDataSource协议并无按照那些作,他们是把sender放在第一位的,例如:
|
![]() weizhe72
|
Rule 21:在通知方法中把sender放到第一位一个真正的委托(Deletage)协议不是用来查询,而是用来通知的。在这种状况下,你应该将sender放到第一位(参考 “说出谁正在说话” 规则)。
取而代之,你应该说出谁正在说话。这是一个习惯,能够很方便地将查询(数据源)与通知(委托)方法区分开来。 |
![]() weizhe72
|
规则 22:若是约定被打破了,那就抛开它吧! 上面已经说了这么多,记住约定和一致性必定是在某些时刻要屈服于优秀的观点的 - 在这个例子当中,或者你的例子中。若是约定被打破,那就好不担忧的去跳过它。若是你的观点真的更好,那就去作吧! 举个例子,在菜单控件中已经存在一个约定了,靠这个约定你可以经过代理来使菜单选项可用或者不可用,利用calledvalidateMenuItem:方法。为了一致性的缘故,我曾考虑过给我代理协议中的一部分方法用相同的名字。可是我最终决定没有那样作,由于:
相反的,我继续为了更简单而且更加的容易理解而打破了约定:
咱们可以为了特殊的语法而战,可是当你遇到那个方法时,你应该马上就知道它是作什么的?如何来使用它。我认为,那样会更好。 |
![]() 李远超
|
通知通知是委托协议的另外一部分。我认为,若是你使用委托协议(若是合适,你应当用它),你最好加上通知,否则它不算完整。 在 MGTileMenu 中,你能够在 MGTileMenuController 的接口文件 中找到的通知。 |
![]() Khiyuan
|
规则 23:通知和代理方法并行 在代理方法(注意;不是数据源方法)和通知之间有一个剪不断理还乱的联系。在你的代码中一样的地方你会同时用到它们,而且起到一样的做用。 若是一个代理方法告诉代理发生了一些事情,你一般应该为了起到相同的做用发送一个通知。就像代理方法同样加上通知,去除掉模棱两可的方法,而且落实你的通知列表。 代理方法的参数应该和通知的userInfo的内容相匹配,这是很明显的除非你做为对象来传递sender,而不是捆绑在字典信息中。 代理方法:
|
![]() 李远超
|
||||||||
其它翻译版本(1) |
Rule 24: 通知关联的 userInfo 要尽可能详细尽可能为通知提供有用的信息。记住:通知接收方(很)可能与你组件里面的代理方法或者数据源链彻底无关。 你要想一想到底哪些信息会有用,并提供相应的信息。至少,你必需确保代理方法的全部参数都包含在 userInfo 对象里。 代理方法:
|
![]() daxiaoming
|
Rule 25: 最终测试 最终有些事咱们已经知道了. 软件工程和敬业精神的第101条: 确保它真地有效. 是否用正式TDD测试取决于你,可是测试是不可缺乏的。每一个可选的委托方法。每个发布的通知。定制的每一个点,在每个可能的组合。组件提供了一千个微妙的问题的机会。 可能有一些缺陷。找到他们,先解决这些问题。若是你的时间推后,切功能,而不是调试。你要受的苦没有出货的错误。 |
![]() 寂寞沙洲
|
最后的思考我制定上述的规则是经过我本身这几年在建立components和APIs时所犯的错误中总结的。我也努力的尝试的遵照我制定的规则,可是不免的在某些状况下我没有。 虽然不可能在每一个场景or每一个事件中应用到这些规则,可是若是尽量的遵循这些规则,你将可能创造出一个设计良好的,灵活,可重用的components。让其余人一块儿享用的components。 你也许想要一个简单的提纲关于这些规则,下面的图片就是。我有全尺寸的图片版本托管在Flickr上。 若是你喜欢发布本身的components给别人去使用,就像个人MGTileMenu同样。那么也许你也想去读读我发的open source code(开源代码),其中几个点也许会触及到。这篇文章也讨论了一些README文件,协议选择的相关事宜。 |