我最常做的开发任务是设计一个可重用的API组件。组件通常为iOS(尽管有时它们是OS X) 设计的,且总是GUI控件或某种视图。
多年来,我为客户开发了很多API组件,其中包括像Apple这样的客户,而且我已经很了解这个过程。我也定期发布开源组件,并且我把曾经对我有帮助的资料和API设计指南放在一起与大家分享。
这是一个重要的主题,无论你是一个开源贡献者,或作为团队的一员参与开发大型的应用,或者只是设计自己的软件。正如开发一个应用的过程,API接口是使用你代码的开发者对你代码的第一印象,将严重影响着开发者决定是使用或扔掉它。
APIs是开发者的用户体验。我一直惊讶,具体到这个流行平台上没有很多的资料是写我们这方面工作的。
当我们阅读一些设计指南时,必要的时候,我将要用我最近发布的开源GUI组件MGTileMenu作为一个例子。你可以在这里先阅读所有关于MGTileMenu的信息,如果你喜欢。
应用程序接口(API)设计和用户界面、用户体验设计很相像。你的目标用户有不同的需求和特点,但归根结底他们的目标还是把需求完成而已。就像一个设计友好、易用的应用程序的用户界面一样,你需要让你的API有以下的特点:
如同人们设计的其它的软件一样,我们首先需要考虑的是使用案列。我们的设计需要使最经常被用到的的功能简单易用,不需要过度的配置。在默认配置下软件就应该是可用的,并且具有一定的可配置性。软件的设计应该具有可探索性,而且应该允许用户从已知的的范例中推广到其他应用场景。这和我们创建一个用户界面的规则非常的相像。
用于和开发者交互的元素使用四个主要的显示意味着:
我们需要把每一个都设计成:明智和慎重的,用于人类使用。这里有2个问题当你设计API的时候需要考虑:
我们的核心原则是让已有的类和模型保持一致性,以用来保证我们可以把一个开发者不熟悉的控制让他很轻松的在他可以理解的平台上使用。使用标准APIs,模型,和模式无论是不是可能(并且这个应该是总用的)。对于终端用户,熟悉和直觉性是和代码层级一样重要的。
让我们看看我们之前提到的这四个元素:
Here’s the interface file for MGTileMenu.
在我们讨论具体的接口之前,这有一些涵盖范围比较广泛的规则:
我所看到最常见的错误是API的设计利用了外来的约定。APIs 属于固定平台和固定的开发者生态系统。你根本无法使用任何习语和你用过的其他平台的架构,这样做会污染您当前的代码库,并对其他开发人员的效率造成损害。
在coding之前要了解你目标平台的约定,比如,在iOS 或者 OS X,不使用异常对待control的流程 。以适当的方式命名你的方法(通常指有足够详细,但也应该有足够的简洁)。
了解协议,和委托,类别分别是什么。在你的代码中使用他们。学习相关的构造函数和析构函数的命名方案。请遵守内存管理规则。词汇和语法是不可分割的,你要么发展为一个固定的的平台,或者你跨平台。
规则 2:解耦的设计
任何一个组件都应该被设计成不和它所服务的工程相耦合,并且如果它是GUI控件或者是View,它至少应该默认情况下显示一些东西。利用已经存在的框架作为指导,利用代理协议维持松耦合,精心设计/命名API的方法,还有合时合处的通知。
为了达到松耦合显而易见但却是高效的方法是为每一个组件创建一个新的工程,独立地开发这个组件。强制你自己用你自己的API。远离将一些不相干的类放到一起的诱惑。开始并继续你想做的。
依据这种观点,让我们深度的讨论一下接口类。初始化方法是接口中最重要的部分之一,因为它们展示了人们如何开始你的组件。你的类为了初始化配置当然会需要一些设置。所以,一个显而易见的规则如下:
如果有什么需要设置的,不要等待 -需要它了就去做,如果你没有得到的东西的立即返回nil。
- (id)initWithDelegate:(id<MGTileMenuDelegate>)theDelegate; // required parameter; cannot be nil.
这个前一个结果的必然结果: 记住不要仅仅传入参数,应该可以通过属性或者赋值来访问他们,如果他们可以通过任何方式来一场“按摩”(修改,重写等)
@property (nonatomic, weak, readonly) id<MGTileMenuDelegate> delegate; // must be specified via initializer method.前两个例子阐述了这个观点。
实际上,你不总为component提供单独的文档。如果你不提供文档,你的.h文件(包括demo app)就是你的文档。他们应该适当的描述,我的意思是:
特别是,你应该简要注释在属性或访问器旁边;头文件扫描比在初始化实例的时候更容易。
@property (nonatomic) CGGradientRef tileGradient; // gradient to apply to tile backgrounds (default: a lovely blue) @property (nonatomic) NSInteger selectionBorderWidth; // default: 5 pixels @property (nonatomic) CGGradientRef selectionGradient; // default: a subtle white (top) to grey (bottom) gradient
你的类应该设计成只需要最少的代码来集成(包括后续将用到的委托/数据源协议)。但不包括委托方法,你应该着手于用3行代码就可以达到测试的目的。
这3行代码如下:
// Instantiate. tileController = [[MGTileMenuController alloc] initWithDelegate:self]; // Configure. tileController.dismissAfterTileActivated = NO; // to make it easier to play with in the demo app. // Display. [tileController displayMenuCenteredOnPoint:loc inView:self.view];
我对于apps的准则就是:不要让用户去做选择。选择满足多数人的人性化的默认设置,略去参数设置窗口。毕竟,好的软件都是有倾向性的。
由于运用场景不是那么的清晰明确,所以不同的组件面对的情况也有些不同。你当然可以做一个只满足某种特定情况的组件,但是,通常我们都希望有些灵活性。你绝不会准确的知道另一个开发者将会怎样使用你的组件,所以你必须做到有一定的通用性。
认真的选择你的定制点是很重要的。考虑依赖关系更加的重要——不是对编译/链接的理解,而是定制类型之间的逻辑关系。我的方法就是尽量从“方面”的层次上考虑而不是实例变量的层次上。你希望你的组件的那些方面允许被定制化?那么你就知道哪些特定的属性需要暴露。
通过不暴露足够的的配置点,就可以很容易的弱化某个特定的定制类型。例如:3.如果没有空间,就不要暴露大小。
具体的情况取决于具体的组件,但是需要从外观或者功能角度来考虑属性之间的关系。学会理解开发者。不要禁止组件的个性化,让它灵活些。
@property (nonatomic) BOOL dismissAfterTileActivated; // automatically dismiss menu after a tile is activated (YES; default) @property (nonatomic) BOOL rightHanded; // leave gap for right-handed finger (YES; default) or left-handed (NO) @property (nonatomic) NSInteger tileSide; // width and height of each tile, in pixels (default 72 pixels) @property (nonatomic) NSInteger tileGap; // horizontal and vertical gaps between tiles, in pixels (default: 20 pixels) @property (nonatomic) CGFloat cornerRadius; // corner radius for bezel and all tiles, in pixe让常识来指导你。确定那些能够满足70%左右你所能想到的使用场景的选项,然后提供这些选项。剩下的就让你的授权方法和代码架构来满足吧。
评论删除后,数据将无法恢复
评论(7)