使用不熟悉的对象时,弄清楚到哪里去找所需的内容可能会比较困难。尽管在一个类中成员函数的排序是一种风格问题,但是从用户的角度看处理好排序有助于保持一致性。对成员功能进行分类的一个基本方法是看它是否潜在地影响了对象的状态。
在图1-5中描述了一个对开发者和用户都有帮助的组织。这个组织具有根据功能种类进行分组的优点,这种分组方式几乎在每个C++类中都存在。这个组织也独立于所实现的特定抽象。
生成函数(CREATOR)生成对象或消除对象。注意,operator=不是一个生成函数,但却是(习惯上)第一个操纵函数;操纵函数(MANIPULATORS)就是非常量成员函数;访问函数(ACCESSORS)是常量成员函数。这种纯粹客观的分组,是否所有的访问函数都声明为类的常量函数,是否所有的操纵函数都不声明为类的常量函数,十分易于验证。其主要优势是为仔细检查不熟悉类的基本功能提供一个共同的起点。对于更大规模的类,在每个部件内将成员按字母顺序排序很好。对于像包装器(将在5.10节和6.4.3节论述)这样的非常大型的类,其他组织方式可能更合适。
有时,人们设法将成员函数组成get/set对,如图1-6所示。某些用户错误地认为一个对象只不过就是一个有数据成员的公共数据结构,所以才采用这种风格。每一个数据成员都必须既有一个“get”函数(访问函数)又有一个“set”函数(操纵函数)——这种风格本身可能会阻碍封装接口的合理创建,在合理封装的接口中,数据成员没有必要透明地反映在对象的行为中。
最后,还有在哪里放置数据成员的问题。完全封装好的类没有公共数据。从逻辑角度看,数据成员只是类的实现细节。因此,许多人都愿意把类的实现细节(包括数据成员)放在类定义的末尾,如图1-7所示。
这个组织试图在类定义的尾部隐藏实现细节,对于缺乏经验的客户可能更具有可读性,但是这些实现细节实际上并没有被隐藏。头文件中实现细节的存在会影响编译时耦合的程度,这种编译时耦合不会因为在类定义中重新放置了这些细节就消失。
由于本书讲述物理和组织的设计问题,我们始终把头文件中的实现细节放在公共接口的前面(这一部分是为了强调实现细节的存在)。在第6章,我们将论述如何从一个头文件中彻底地移除这种实现层的混乱内容,让实现层实际对用户处于隐藏状态。