类里面是什么?-接口原则
This article appeared in C++ Report, 10(3), March 1998.
我开始于一个简单而令人困惑的问题:
l 类里面是什么?也就是,什么组成了类及其接口?
更深层的问题是:
l 它怎样和C风格的面向对象编程联系起来?
l 它怎样和C++的Koenig lookup联系起来?和称为Myers Example的例子又有什么联系?(我都会讨论。)
l 它怎样影响我们分析class间的依赖关系以及设计对象模型?
那么,“class里面是什么?”
首先,回顾一下一个class的传统定义:
Class(定义)
一个class描述了一组数据及操作这些数据的函数。
程序员们常常无意中曲解了这个定义,改为:“噢,一个class,也就是出现在其定义体中的东西--成员数据和成员函数。”于是事情变味了,因为它限定“函数”为只是“成员函数”。看这个:
//*** Example 1 (a)
class X { /*...*/ };
/*...*/
void f( const X& );
问题是:f是X的一部分吗?一些人会立即答“No”,因为f是一个非成员函数(或“自由函数”)。另外一些人则认识到关键性的东西:如果Example 1 (a)的代码出现在一个头文件中,也就是:
//*** Example 1 (b)
class X { /*...*/
public:
void f() const;
};
再考虑一下。不考虑访问权限〖注1〗,f还是相同的,接受一个指向X的指针或引用。只不过现在这个参数是隐含的。于是,如果Example 1 (a)全部出现在同一头文件中,我们将发现,虽然f不是X的成员函数,但强烈地依赖于X。我将在下一节展示这个关联关系的真正含义。
换种方式,如果X和f不出现在同一头文件中,那么f只不过是个早已存在的用户函数,而不是X的一部分(即使f需要一个X类型的实参)。我们只是例行公事般写了一个函数,其形参类型来自于库函数头文件,很明确,我们自己的函数不是库函数中的类的一部分。
The Interface Principle
接口原则(Interface Principle)
依靠那个例子,我将提出接口原则:
接口原则
对于一个类X,所有的函数,包括自由函数,只要同时满足
(a) “提到”X,并且
(b) 与X“同期提供”
那么就是X的逻辑组成部分,因为它们形成了X的接口。
根据定义,所有成员函数都是X的“组成部分”:
(a) 每个成员函数都必须“提到”X(非static的成员函数有一个隐含参数,类型是X*(WQ:C++标准制定后,精确地说,应该是X* const)或const X*(WQ:const X* const);static的成员函数也是在X的作用范围内的);并且
(b) 每个成员函数都与X“同期提供”(在X的定义体中)。
用接口原则来分析Example 1 (a),其结论和我们最初的看法相同:很明确,f“提到”X。如果f也与X“同期提供”(例如,它们存在于相同的头文件和/或命名空间中〖注2〗),根据接口原则,f是X的逻辑组成部分,因为它是X的接口的一部分。
接口原则在判断什么是一个class的组成部分时,是个有用的试金石。把自由函数判定为一个class的组成部分,这违反直觉吗?那么,给这个例子再加些砝码,将f改得更常见些:
//*** Example 1 (c)
class X { /*...*/ };
/*...*/
ostream& operator<<( ostream&, const X& );
现在,接口原则的基本理念就很清楚了,因为我们理解这个特别的自由函数是怎么回事:如果operator<<与X“同期提供”(例如,它们存在于相同的头文件和/或命名空间中),那么,operator<<是X的逻辑组成部分,因为它是X的接口的一部分。
据此,让我们回到class的传统定义上: