聊聊 API 设计

153 views
Skip to first unread message

redsea

unread,
Sep 22, 2007, 3:17:09 AM9/22/07
to TopLanguage
API 的设计, 严重影响程序的实现和维护. 我最早看到强调 API 设计的书是 微软的 <<编程精粹>>, 那本书主要是从 SP 的角度来
谈 API 设计的, 读完我觉得很有收获.

我先来抛砖引玉:

A: 耦合度较高一组类的处理
如果一组类, 原本就是密切配合工作, 互相知道对方的存在, 无法独立使用, 那么它们之间使用 interface 做接口并没有什么不合适
的, 具体可以依据源程序分割, 性能要求等因素, 决定采用interface 还是其他class, 或者 GP,

B: 耦合度不高的类的处理

设计中, 原本来两个类之间关系不密切的, 采用 interface, 我的看法是很不合适. 白白加大耦合度和增加程序里面概念的数量
---- 多了很多香炉和鬼 ;)

不密切的种类:
1. 松耦合的 provide/consumer, 简单如 ponda 在此帖中的例子. 这应该使用 简单的 delegate 或者复杂
一点的 singal/slot 都可以.

2. 两个业务领域模块的接口, 我近来是使用 delegate (C++ 里面是 boost::bind,
boost::function 之类的) /free function,
甚至一个 struct 里面一堆的delegate, free function (好像有些朋友很烦这个)

3. ????
4. ????

其他情形呢 ?

C. 什么时候应该将算法单独用 gp 实现呢 ? (例如上次讨论的 collect)


请大家的玉砸过来, 我好收起卖钱 :)

Huafeng Mo

unread,
Sep 22, 2007, 6:06:16 AM9/22/07
to pon...@googlegroups.com
砖头来了。(玉已经卖钱了)。:-)

C. 什么时候应该将算法单独用 gp 实现呢 ? (例如上次讨论的 collect)
==================
我的感觉是,需要代码复用的时候。

red...@gmail.com

unread,
Sep 22, 2007, 6:19:54 AM9/22/07
to pon...@googlegroups.com
Huafeng Mo 写道:

> 砖头来了。(玉已经卖钱了)。:-)
> C. 什么时候应该将算法单独用 gp 实现呢 ? (例如上次讨论的 collect)
> ==================
> 我的感觉是,需要代码复用的时候。

我这样理解你的话: 如果目前或者可见的将来, 多于一个类可能要用到同样的算
法, 我们不要去将他们弄成同一个基类, 然后将代码抽过去; 或者将他们弄出同一
个 interface 来, 而是写一个 GP 算法.

我想想, 有以下进一步的问题:

如果两个类很确切地能够并且应该抽出一个基类来, 那么似乎不必讨论, 我们就抽
出一个基类来好了.

除此外, 什么情形下, 我们弄出同一个 interface 来比 GP 算法会更好呢 ?

yq chen

unread,
Sep 22, 2007, 6:47:53 AM9/22/07
to pon...@googlegroups.com
有一种情况,对象是根据外部的相关信息创建的,如果你的代码需要使用该对象,那你很可能需要一个interface。不同的外部信息可能创建不同的对象,但所有这些对象都需要实现你定义的interface。在你的代码中通过该interface将你的业务逻辑分派下去,通过虚函数机制你就可以调用到当前具体对象相应的方法。

在07-9-22,red...@gmail.com <red...@gmail.com> 写道:

red...@gmail.com

unread,
Sep 22, 2007, 6:59:06 AM9/22/07
to pon...@googlegroups.com
这是动多态了, 此处 GP 不能取代 OOP, 只能用 虚函数 (类 或者 interface),
几乎就没有别的好方法了.


yq chen 写道:
> 有一种情况,对象是根据外部的相关信息创建的,如果你的代码需要使用该对
> 象,那你很可能需要一个interface。不同的外部信息可能创建不同的对象,但
> 所有这些对象都需要实现你定义的interface。在你的代码中通过该interface将
> 你的业务逻辑分派下去,通过虚函数机制你就可以调用到当前具体对象相应的方法。

Huafeng Mo

unread,
Sep 22, 2007, 7:24:19 AM9/22/07
to pon...@googlegroups.com
不错不错,动多态是没法替代的。
gp只能优化(或简化)动多态的实现。但无法替代。
实际上,就像我的例子中给出的那样,gp也并没能替代oop和sp,而是优化了他们的实现。

Huafeng Mo

unread,
Sep 22, 2007, 7:35:13 AM9/22/07
to pon...@googlegroups.com
我的感觉是,一般在不涉及动多态的情况下,gp差不多可以替代oop的接口,因为gp的concept也是一种接口,但却是非绑定的(没有同一种具体的类型绑定)。灵活性更大。我做个一些尝试,基本上还行,就是编译时间...。还好,我用vc8比较多,这个问题不算太大,只要不倒腾元编程,还是可以忍受的。d就不存在这种障碍了。
说起绑定,我觉得《C++ Templates》里的说法值得考虑:多态有两各方面的特性:动/静性和绑定性。两者组合,有四种形式:动态绑定多态,也就是基于oop虚函数的多态;静态未绑定的多态,也就是模板;动态的非绑定多态,好像就是ruby的火星人类型系统;静态的绑定多态,忘了,书不在手边,查不到,哪位知道言语一声。:)

On 9/22/07, red...@gmail.com <red...@gmail.com> wrote:

redsea

unread,
Sep 22, 2007, 10:44:59 AM9/22/07
to TopLanguage
我买了这本书, 但是以前没有认真看.
听你提起, 我翻了翻,

-------------------------------
P231 :
C++ 中
通过继承实现的多态是绑定和动态的.
通过模板实现的多态是非绑定和静态的.

其他语言中, 可能会有其他组合, 例如 Smalltalk 提供了非绑定的动态多态. 但是 C++ 的上下文中, 动多态和静多态是两个非常准
确的概念, 并不会产生混淆.

P236
事实上, 泛型程序设计是相当实用的, 因为它所依赖的是静多态, 而静多态会要求在编译期对接口进行解析. 另一方面, 这种要求还会带来一些与面向
对象设计原则截然不同的新设计原则.
-------------------------------

C++ 在OO其实不是太好用, 由于没有 gc; 构造函数不能调用虚函数; 构造函数出现异常, 资源释放比较麻烦; rtti 较弱 等等.

C++ 近年的发展, 包括 C++ 0x 基本上集中在泛型编程上了, 这里面有很多有价值的地方, 但大多数人用得还很少.

boost 里面倒是用得很充分, 但是那种用法, 能够将潜在的用户统统吓走.


On 9月22日, 下午7时35分, "Huafeng Mo" <longshank...@gmail.com> wrote:
> 我的感觉是,一般在不涉及动多态的情况下,gp差不多可以替代oop的接口,因为gp的concept也是一种接口,但却是非绑定的(没有同一种具体的类型绑定 )。灵活性更大。我做个一些尝试,基本上还行,就是编译时间...。还好,我用vc8比较多,这个问题不算太大,只要不倒腾元编程,还是可以忍受的。d就不存在 这种障碍了。
> 说起绑定,我觉得《C++

> Templates》里的说法值得考虑:多态有两各方面的特性:动/静性和绑定性。两者组合,有四种形式:动态绑定多态,也就是基于oop虚函数的多态;静态未 绑定的多态,也就是模板;动态的非绑定多态,好像就是ruby的火星人类型系统;静态的绑定多态,忘了,书不在手边,查不到,哪位知道言语一声。:)
>

Atry

unread,
Sep 22, 2007, 11:01:50 AM9/22/07
to pon...@googlegroups.com
动态绑定多态,所有的脚本语言都是那样啊, JavaScript Lua 都是那样的,用哈希表来代替结构。ActionScript 1.0 和 2.0 也完全是哈希表的,到 3.0 默认没有哈希表,不是动态的,需要加上 dynamic 关键字才是动态的。

在07-9-22,redsea < red...@gmail.com> 写道:

yq chen

unread,
Sep 22, 2007, 11:03:39 AM9/22/07
to pon...@googlegroups.com
reasea 写到:
构造函数不能调用虚函数;
 
其实构造函数是可以调用虚函数的,但是不能多态的调用而已,它只能调用自己的函数实现。

Huafeng Mo

unread,
Sep 22, 2007, 6:40:14 PM9/22/07
to pon...@googlegroups.com
这可是一本经典著作啊,我死活都买不到这本书,羡慕啊。我有电子版的,可是不清楚,只能对照原版的看。两作者可都是老牛了:David Vanderevoorde, Nicolai M. Josuttis。前者在C++的proposal里到处是他的名字,后者的那本C++ Standard Library则具有收藏价值。
现在业界对泛型编程的价值尚未有充分的认识。这一方面由于Java的搅局(让那些懵懂的程序员一头扎进oop里,不能自拔);另一方面,人对事物认识需要一个过程,很多人的"魂还在后面"呢。:-)

On 9/22/07, redsea <red...@gmail.com> wrote:

pi1ot

unread,
Oct 6, 2007, 3:20:54 AM10/6/07
to TopLanguage
我觉得《c++代码设计与重用》这本书不错。
我自己的做法,接口做的事情尽量简单,基本上看到接口名(函数名)就知道他是干嘛的,多一分的事情都不作,然后提供一系列的简单接口完成一个复杂工作。
尽量让各个接口内部的重复任务降低到0。
关于易用性,我以前习惯用c/c++的默认参数,这样最常见的一般任务直接调用,复杂任务才需要把非默认的参数值传递过去。接口一般性的不伤筋动骨的升
级后可以通过添加新的默认参数的形式避免重新编译旧的调用代码。
但是最近项目规模大了,发现太多的默认参数在维护性上有一些问题,还没有太好的想法。

red...@gmail.com

unread,
Oct 6, 2007, 6:35:33 AM10/6/07
to pon...@googlegroups.com
pi1ot 写道:

> 关于易用性,我以前习惯用c/c++的默认参数,这样最常见的一般任务直接调用,复杂任务才需要把非默认的参数值传递过去。接口一般性的不伤筋动骨的升
> 级后可以通过添加新的默认参数的形式避免重新编译旧的调用代码。
> 但是最近项目规模大了,发现太多的默认参数在维护性上有一些问题,还没有太好的想法。
>
>
有很多参数, 其中很多又有默认的, 我记得一般有两种处理方法(可以结合用的两种)
1. 将逻辑上密切相关的参数组织成一个有业务含义的结构使用

2. 将逻辑上相关的参数组织成一个结构, 同时给这个结构提供 n 多种构造方法,
各个构造方法可以根据调用的情况, 选择对应不同成员变量的形参, 还可以选择形
参的不同类型. 这样, 参数管理就独立于函数体了.

Atry

unread,
Oct 6, 2007, 8:41:13 AM10/6/07
to pon...@googlegroups.com
参数过多往往暗示你的接口有问题

在07-10-6,pi1ot <pilo...@gmail.com> 写道:

yq chen

unread,
Oct 6, 2007, 9:27:25 AM10/6/07
to pon...@googlegroups.com
对于参数过多的问题,winapi是一个典型的例子。在结构化的语言中一般将多个逻辑相关的参数抽象成一个结构;而在有对象概念的语言里,一般都把跟类核心概念相关的一组函数,抽象成类的方法,而把这一组函数中共同概念的参数变成类的状态。
 
但是就算把这些东西都抽象出来,还是有一些闲散的参数无法划分,比如CreateProcess、CreateWindow(Ex)等等。作为API来说,它为了实现普适性,必须提供这些参数,由于C语言中没有缺省参数这样的东西,用户要么不初始化,要么就得一个个的写。这个时候有缺省参数的语言(如C++、python等)就提供了很多方便性。通过对API参数中最不常用的参数排序,把不需要钉子的参数放到最后,给出常用的缺省参数。这个时候就能使API在极大多数情况下能够很容易的被使用同时能够满足少数特殊需要定制的应用。
 
在这个方面,java社区和C#社区曾有不同的看法,他们认为提供缺省参数同时又提供函数重载容易引发错误。但是我认为就算有函数重载,仍然不能象缺省参数那样优雅地解决API简单化的问题。

 
在07-10-6,Atry <pop....@gmail.com> 写道:

pongba

unread,
Oct 6, 2007, 9:36:02 AM10/6/07
to pon...@googlegroups.com


On 10/6/07, yq chen <mephist...@gmail.com> wrote:
对于参数过多的问题,winapi是一个典型的例子。在结构化的语言中一般将多个逻辑相关的参数抽象成一个结构;而在有对象概念的语言里,一般都把跟类核心概念相关的一组函数,抽象成类的方法,而把这一组函数中共同概念的参数变成类的状态。
 
但是就算把这些东西都抽象出来,还是有一些闲散的参数无法划分,比如CreateProcess、CreateWindow(Ex)等等。作为API来说,它为了实现普适性,必须提供这些参数,由于C语言中没有缺省参数这样的东西,用户要么不初始化,要么就得一个个的写。这个时候有缺省参数的语言(如C++、python等)就提供了很多方便性。通过对API参数中最不常用的参数排序,把不需要钉子的参数放到最后,给出常用的缺省参数。这个时候就能使API在极大多数情况下能够很容易的被使用同时能够满足少数特殊需要定制的应用。
 
在这个方面,java社区和C#社区曾有不同的看法,他们认为提供缺省参数同时又提供函数重载容易引发错误。但是我认为就算有函数重载,仍然不能象缺省参数那样优雅地解决API简单化的问题。
补充一下。我记得C99提供了named arguments特性了吧?这样参数的顺序就可以不考虑了,提供缺省参数也大大方便了,以前的缺省参数机制是有缺陷的,如果我要仅覆盖地M个缺省参数,那么前M-1个缺省参数还必须都写出来。

--
刘未鹏(pongba)|C++的罗浮宫
http://blog.csdn.net/pongba
TopLanguage
http://groups.google.com/group/pongba

yq chen

unread,
Oct 6, 2007, 9:39:30 AM10/6/07
to pon...@googlegroups.com
是的,python中也提供这种基于参数名称的缺省参数,当然这个更为好用。但不知道这样的基于key的参数,是否会用到运行时。

在07-10-6,pongba <pon...@gmail.com> 写道:

pi1ot

unread,
Oct 6, 2007, 10:08:46 PM10/6/07
to TopLanguage
这两天在看机械出版社的the old new thing,关于windows进化中的一些背景细节。看了其中涉及到api兼容性的章节后,我的感觉
是,一旦发布了一个接口,那你就做好准备应付他人以各种各样的你想不到的千奇百怪的花样来调用这个接口的方式,而且必须负责到底。
从这个角度来说,尽量简化接口工作和参数其实也是一种偷懒,至少调用方搞不出什么花样出来了。
Reply all
Reply to author
Forward
0 new messages