免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1780 | 回复: 0
打印 上一主题 下一主题

Objective-C 的 API 设计 [复制链接]

论坛徽章:
49
15-16赛季CBA联赛之福建
日期:2016-06-22 16:22:002015年亚洲杯之中国
日期:2015-01-23 16:25:12丑牛
日期:2015-01-20 09:39:23未羊
日期:2015-01-14 23:55:57巳蛇
日期:2015-01-06 18:21:36双鱼座
日期:2015-01-02 22:04:33午马
日期:2014-11-25 09:58:35辰龙
日期:2014-11-18 10:40:07寅虎
日期:2014-11-13 22:47:15申猴
日期:2014-10-22 15:29:50摩羯座
日期:2014-08-27 10:49:43辰龙
日期:2014-08-21 10:47:58
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2013-03-09 07:38 |只看该作者 |倒序浏览
  
   
        
            
            
            
我最常做的开发任务是设计一个可重用的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.
                
    在我们讨论具体的接口之前,这有一些涵盖范围比较广泛的规则:
                Rule 1: 使用方言
                
    我所看到最常见的错误是API的设计利用了外来的约定。APIs 属于固定平台和固定的开发者生态系统。你根本无法使用任何习语和你用过的其他平台的架构,这样做会污染您当前的代码库,并对其他开发人员的效率造成损害。
                
    在coding之前要了解你目标平台的约定,比如,在iOS 或者 OS X,不使用异常对待control的流程 。以适当的方式命名你的方法(通常指有足够详细,但也应该有足够的简洁)。
                
    了解协议,和委托,类别分别是什么。在你的代码中使用他们。学习相关的构造函数和析构函数的命名方案。请遵守内存管理规则。词汇和语法是不可分割的,你要么发展为一个固定的的平台,或者你跨平台。
                
                
            
       
       
            
                
                
                Rule 2: 设计解耦
                
    任何component的设计应该没有连接到你当前创建的项目,如果他是一个GUI control或者一个视图,它应该默认显示一些东西。使用现有的框架作为一个指南,与委托协议,精心设计的/命名的API方法和通知在适当的地方保持松耦合。
                
    一个很明显的,但非常有效的方式,是每次为你的component创建一个项目,并逐渐的隔离开发component。强迫自己使用自己的API。远离无关的类。
                
    接下来,让我们来适当谈谈类的接口。初始化方法的接口中最重要的部分之一,因为他们是人们如何开始使用您的组件。你的类将有一定的初始配置所需的设置。所以,一个明显的规律:
                
                
            
       
       
            
                
                
                Rule 3: 必须设置初始化参数
                
    如果有什么需要设置的,不要等待 -需要它了就去做,如果你没有得到的东西的立即返回nil。
                
                
                
                
                   
                        
                            1
                            - (id)initWithDelegate:(id)theDelegate; // required parameter; cannot be nil.
                        
                   
                
                
                
                
                Rule 4: 允许访问初始化参数Allow access to initializer parameters
                
    这个前一个结果的必然结果: 记住不要仅仅传入参数,应该可以通过属性或者赋值来访问他们,如果他们可以通过任何方式来一场“按摩”(修改,重写等)
                
                
                
                
                   
                        
                            1
                            @property (nonatomic, weak, readonly) id delegate; // must be specified via initializer method.
                        
                   
                
                
                
                
                前两个例子阐述了这个观点。
                
            
       
       
            
                
                
                Rule 5: 注释你的header文件 (包含默认值)
                
    实际上,你不总为component提供单独的文档。如果你不提供文档,你的.h文件(包括demo app)就是你的文档。他们应该适当的描述,我的意思是:
                
                   
  • 足以描述,但是不是特别多,要简洁。
                   
  • 一切是提供给专业人士,所以适当的描述别描述无关的事情。
                
                
    特别是,你应该简要注释在属性或访问器旁边;头文件扫描比在初始化实例的时候更容易。
                
                
                
                
                   
                        
                            1
                            @property (nonatomic) CGGradientRef tileGradient; // gradient to apply to tile backgrounds (default: a lovely blue)
                        
                   
                
                
                
                
                   
                        
                            2
                            @property (nonatomic) NSInteger selectionBorderWidth; // default: 5 pixels
                        
                   
                
                
                
                
                   
                        
                            3
                            @property (nonatomic) CGGradientRef selectionGradient; // default: a subtle white (top) to grey (bottom) gradient
                        
                   
                
                
                
                
                
                
            
       
       
            
                
                
                Rule 6: 习惯于运行3行代码
                
    你的类应该设计成只需要最少的代码来集成(包括后续将用到的委托/数据源协议)。但不包括委托方法,你应该着手于用3行代码就可以达到测试的目的。
                
    这3行代码如下:
                
                   
  • 实例化你的类
                   
  • 基本配置,使之能够展示一些信息
                   
  • 展示或激活它(类的示例)
                
                就这样。任何实质上更繁杂的就会导致代码坏味(code smell)。下面是 MGTileMenu’s demo app中相关的几行代码:
                

                
                
                
                
                   
                        
                            1
                            // Instantiate.
                        
                   
                
                
                
                
                   
                        
                            2
                            tileController = [[MGTileMenuController alloc] initWithDelegate:self];
                        
                   
                
                
                
                
                   
                        
                            3
                            
                        
                   
                
                
                
                
                   
                        
                            4
                            // Configure.
                        
                   
                
                
                
                
                   
                        
                            5
                            tileController.dismissAfterTileActivated = NO; // to make it easier to play with in the demo app.
                        
                   
                
                
                
                
                   
                        
                            6
                            
                        
                   
                
                
                
                
                   
                        
                            7
                            // Display.
                        
                   
                
                
                
                
                   
                        
                            8
                            [tileController displayMenuCenteredOnPoint:loc inView:self.view];
                        
                   
                
                
                
                
                
                
            
       
       
            
                
                
                Rule 7: 臃肿的demo通常意味着组件是糟糕的
                
    另一个推论:您的demo的大小是衡量你component质量的标准,其值越小越好。Demo/Code 应该尽可能的小巧而又精简(用于演示,旨在描述所有组件的定制或功能)。
                
    核心思想是当你的代码从你的空的Xcode项目模板到你的demo中应该保持最小化的修改。这并不是一个好的借口当你需要复制粘贴demo来让你的component运行。
                
                
            
       
       
            
                
                
                Rule 8:分析特定的场景
                
    我对于apps的准则就是:不要让用户去做选择。选择满足多数人的人性化的默认设置,略去参数设置窗口。毕竟,好的软件都是有倾向性的。
                
    由于运用场景不是那么的清晰明确,所以不同的组件面对的情况也有些不同。你当然可以做一个只满足某种特定情况的组件,但是,通常我们都希望有些灵活性。你绝不会准确的知道另一个开发者将会怎样使用你的组件,所以你必须做到有一定的通用性。
                
    认真的选择你的定制点是很重要的。考虑依赖关系更加的重要——不是对编译/链接的理解,而是定制类型之间的逻辑关系。我的方法就是尽量从“方面”的层次上考虑而不是实例变量的层次上。你希望你的组件的那些方面允许被定制化?那么你就知道哪些特定的属性需要暴露。
                通过不暴露足够的的配置点,就可以很容易的弱化某个特定的定制类型。例如:
                1.如果没有考虑圆角半径,就不要暴露宽度和高度。
                2.如果没有高亮的背景颜色,就不要暴露背景颜色。
                
    3.如果没有空间,就不要暴露大小。
                
    具体的情况取决于具体的组件,但是需要从外观或者功能角度来考虑属性之间的关系。学会理解开发者。不要禁止组件的个性化,让它灵活些。
                
                
                
                
                   
                        
                            1
                            @property (nonatomic) BOOL dismissAfterTileActivated; // automatically dismiss menu after a tile is activated (YES; default)
                        
                   
                
                
                
                
                   
                        
                            2
                            @property (nonatomic) BOOL rightHanded; // leave gap for right-handed finger (YES; default) or left-handed (NO)
                        
                   
                
                
                
                
                   
                        
                            3
                            
                        
                   
                
                
                
                
                   
                        
                            4
                            @property (nonatomic) NSInteger tileSide; // width and height of each tile, in pixels (default 72 pixels)
                        
                   
                
                
                
                
                   
                        
                            5
                            @property (nonatomic) NSInteger tileGap; // horizontal and vertical gaps between tiles, in pixels (default: 20 pixels)
                        
                   
                
                
                
                
                   
                        
                            6
                            @property (nonatomic) CGFloat cornerRadius; // corner radius for bezel and all tiles, in pixe
                        
                   
                
                
                
                
                让常识来指导你。确定那些能够满足70%左右你所能想到的使用场景的选项,然后提供这些选项。剩下的就让你的授权方法和代码架构来满足吧。
                
            
       
       
            
                
                
                Rule 9:  多点属性,少点方法
                
    有一个特定的模式持续出现在我所喜欢的一些来自标准库、开源的第三方以及我自己的一些代码组件。它是一个组件中属性(或者访问器,定制点)个数与方法(也就是所有其它的,从初始化到状态更新)个数的比率。
                
    多属性少方法(再申明一次,方法不是指在Interface Builder中的那些)。MGTileMenu有一个初始化函数和四个实际上供公共使用的愿意非常(每一个都很方便调用另一个方法)。对定制点而言,它的比率有4倍之多。我认为这是一个非常好的比率,使组件不但在功能上变得简洁,而且在定制时更加灵活。
                
                
                
                
                   
                        
                            1
                            - (id)initWithDelegate:(id)theDelegate; // required parameter; cannot be nil.
                        
                   
                
                
                
                
                   
                        
                            2
                            - (CGPoint)displayMenuPage:(NSInteger)pageNum centeredOnPoint:(CGPoint)centerPt inView:(UIView *)parentView; // zero-based pageNum
                        
                   
                
                
                
                
                   
                        
                            3
                            - (void)dismissMenu;
                        
                   
                
                
                
                
                   
                        
                            4
                            - (void)switchToPage:(NSInteger)pageNum; // zero-based pageNum
                        
                   
                
                
                
                
                
                
            
       
       
            
                
                
                
    Rule 10: 在你的控件中使用控件
                
    一个同时简化组件API和实现的好方法就是在你的实现中使用己有的控件。具有统一的外在并不意味着你不可以使用已经存在的组件(确实,这是软件工程当中的一个基本原则)。
                
    考虑是什么让UITableViewCell和UIButton拥有简单的API接口,发现这是因为它们使用诸如UIImageView和UILabel这样的子控件。你也可以,并且该这样去做,并且如果可行的话使用相应的子控件来使你的类接口保持简单不变。
                
    举个例子,在MGTileMenu中,它的外表是常规的UIButtons(不只是子类)。跟在一个的view中自定义去画它的样式、处理输入事件和支持访问而言,极大地简化了它的实现。
                
                
            
       
       
            
                
                
                Rule 11: 于人方便就是于己方便
                
    你会很自然地在实现的过程中加入合适的方法并下意识地将其设置为私有的。相反,应该考虑是否可以公开这些方法,使这些组件能被集成到别人的应用程序。
                
    对你而言那些如何简单方便地加入一个方法或函数的方式,对开发者而言同样如此。
                
    举例来说,在MGTileMenu中,我创建了这些合适的函数:
                
                
                
                
                   
                        
                            1
                            CGRect MGMinimallyOverlapRects(CGRect inner, CGRect outer, CGFloat padding);
                        
                   
                
                
                
                
                   
                        
                            2
                            
                        
                   
                
                
                
                
                   
                        
                            3
                            CGGradientRef MGCreateGradientWithColors(UIColor *topColorRGB, UIColor *bottomColorRGB); // assumes colors in RGB colorspace
                        
                   
                
                
                
                
                第一个函数可以帮助我移动tile menu使它在父View完全可见(如果它们对菜单提供辅助的用户接口,便可以方便地让其它的开发者使用),第二个返回一个渐变Core Graphics给UIColors,它被我用来设置tiles的默认的背景(同时 当实现MGTileMenu的代理协议时,其它的开发者们发现可以很方便的给tiles配置渐变色)。
                
            
       
       
            
                
                
                Rule 12:魅力可以,魔数却不行。
                
    你迟早都会在你的component中加入一些魅力。人人都希望有大量的Steve Jobs式的直观、宜人、富于掌控力的魅力,但是我所说的却是代码中的一些东西,诸如拥有特殊含义的数字或者值。例如,-1在某项设置或者某个特殊场景中,就有着某个特定的意义。
                
    很好,那样做真的挺好的。不爽的是你的代码中充满一些莫名其妙的原始值,更不爽的是还将它们暴露在API中。如果你正在暴露一些魔数,那么为了便于使用,最好还是用#define或者常量或者其他的东西包装一下它们,这样会让魔数更加的直观和易于理解。
                
                
                
                
                   
                        
                            1
                            // Used for the page-switching tile in methods expecting a tile-number.
                        
                   
                
                
                
                
                   
                        
                            2
                            #define MG_PAGE_SWITCHING_TILE_INDEX -1
                        
                   
                
                
                
                
                
                
            
       
       
            
                
                
                代理和数据源协议
                
    代理协议是奇妙的。它们是实现MVC模式的简单的、熟悉的和灵活的方式,它们更会使你养成松耦合的好习惯并且教你明智的API设计。
                
    这个是 MGTileMenu’s delegate protocol.
                
    有太多经典的代理和数据源协议供我们用到几乎所有的组件中。如果你正在显示数据,这个准确的数据源协议可能更接近于:
                
                   
  • 我有多少事物?
                   
  • 事物X对应的属性Y的值是多少?
                
                
    同样的,在几乎任何的情况中,这个准确代理协议可能采用下面的形式:
                
                   
  • 这个事物应该做那些嘛?
                   
  • 这个事物将要做那些。
                   
  • 这个事物刚好做了那些?
                
                
    这也被称为Should, Will, Did协议模式,这也巧妙的连结了之后的Will-Did
                           
                                   
                        
  • 共3页:

  • 上一页
    1

  • 2

  • 3

  • 下一页
                                   
                           
                           
                           
                                    时间:2013-03-08 08:44来源:开源中国社区 作者:oschina责任编辑:zhangkai

    本文来自ChinaUnix新闻频道,如果查看原文请点:http://news.chinaunix.net/opensource/2013/0308/2665867.shtml
  • 您需要登录后才可以回帖 登录 | 注册

    本版积分规则 发表回复

      

    北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
    未成年举报专区
    中国互联网协会会员  联系我们:huangweiwei@itpub.net
    感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

    清除 Cookies - ChinaUnix - Archiver - WAP - TOP