“封装”的“巅峰”——关于《Effective C++》建议的问题

6
Effective C++的第23条建议:优先使用非成员非友元函数而不是成员函数。
该条目的整个目的在于鼓励封装,以及包装的灵活性和功能扩展性。但我的问题是,当你遵循这个建议时,你会走多远呢?
例如,你可以拥有你的类,私有数据成员,然后采用极简主义方法,只为私有数据成员减少公共函数的访问器和/或更改器。然后,每一个其他函数都可以是非成员函数。
但是,你是否愿意通过到处放置访问器和更改器来增加封装,而可能牺牲代码清晰度?界线在哪里呢?

4
对于那些没有《Effective C++》一书的读者,Meyers在http://www.ddj.com/cpp/184401197 上提出了类似的论点。 - Josh Kelley
5个回答

9
首先,不是所有人都同意这个建议。我认为除了Meyers(编辑:Herb Sutter)之外,我没有见过其他人提出这个建议,而且我只看到它在C ++的上下文中被提出。例如,在Java或C#中创建“非成员非友元函数”实际上是不可能的,因为Java和C#没有自由函数,而Ruby开发人员(例如)更喜欢“人性化界面”,有意地创建执行相同操作的成员函数,而非成员函数可以使调用这些函数的代码更容易。
即使您接受Meyers的建议,即应该优先使用非成员非友元函数而不是成员函数(我认为这是一个好建议,它确实帮助我更好地应用封装来思考从其成员函数中封装类的实现),那也只是要考虑的设计的一个方面。
面向对象设计的关键概念是对象执行某些操作。对象不仅仅是其他代码处理的setter和getter的集合。相反,它应该附加行为-也就是说,它应该有执行操作的方法-并且应该封装它执行这些操作的细节。如果您遵循这种面向对象的方法,那么像您这样将Meyers的建议推到极致会损害封装而不是帮助它:您最终会通过getter和setter公开类的所有内部实现变量,而不是隐藏它们,以便只有类的方法(负责代表类执行操作的代码,这是您拥有类的唯一原因)可以访问它。
因此,回答您的问题,即如何采纳Meyers的建议:如果可以合理地使用类的公共接口来实现非友元非成员函数,则不要不必要地将函数转换为成员函数,但不要破坏类的公共接口并违反其封装性,仅为了避免使某些东西成为成员。并确保您在封装与其他关注点和其他方法之间取得平衡(包括,如果您的团队决定走这条路,全面人性化界面的优缺点)。

2
Herb Sutter同意Meyers的观点,并提供了一个更好的例子:http://www.gotw.ca/gotw/084.htm - Mark Ransom
规则显然以简单明了的方式陈述,以便读者能够记住它们,但我认为它们都应该带着一点怀疑的态度。问题更加开放,没有真正反映个人感受。我只是觉得考虑这个问题很有趣,这篇文章通过澄清面向对象编程的目的(文章标记了C++,所以我想这就是被考虑的)。顺便说一下,我没有团队,甚至没有资格全职工作。 - trikker
Alexander Stepanov似乎也更喜欢非成员函数,甚至建议使用友元函数而不是成员函数。 - Max Lybbert

3
请退一步思考这个类的目的:它要完成什么一个任务?为了最好地完成这个任务,它必须确保什么类不变量?子类化和覆盖在这个类的目的中扮演了什么角色?
所有东西都用访问器和修改器表示绝对是不合适的:这几乎把面向对象编程的状态和行为结合分离,或者掩盖了那些在没有这种“假装的”修改器和访问器的情况下根本不涉及获取或设置属性的行为。
只有访问器和修改器的类是一种特殊情况——也许比传统的C类型的struct更高一步,因为它能够保持一些不变量,但“仅仅”。
在良好的面向对象设计中,大多数类都会有行为——我非常喜欢泛型编程,但使用C++的一个原因是其强大的多种范例混合,其中不能排除面向对象编程!-)

2
实际上,仅提供访问器和修改器来访问类的私有变量并不是最简主义(或者在某种意义上是最简主义,但不是“最相关”的),因为您现在向世界展示了一个更一般的接口。封装的思想是,您的类的接口应该尽可能受限,同时允许客户端代码完成工作。
按照这种方法进行操作可以使将来更改底层实现变得更容易,这也是封装的初衷。

0

一般来说,访问器和修改器背后的想法是变量需要以某种方式受到保护。如果变量具有敏感信息,只应由一个实体更新,您可以在修改器中包含该检查。事实上,大多数访问器和修改器只是一种形式(如此之多,以至于C#现在具有自动属性),并不是必需的。最重要的是,要根据情况判断。如果需要受限制的访问,则添加修改器。如果无论谁获取/设置变量都没有关系,则不需要访问器和修改器。


1
我认为一个类的内部变量不应该被公开。如果你需要调试、检测或重构代码,修改访问器比修改所有用户现在调用访问器更容易(也更清晰)。这只是我的个人看法。 - Vitali

0

如何将事物打包成合适的单元是一门主观的艺术。

我对于将某些东西变为非成员非好友的做法有所保留,因为如果内部结构的更改需要添加新的公共接口来支持现有的非成员或者现在必须承认非成员为好友,那么你就展示了比之前设计的更紧密的联系。这可能会使任何更改都变得代价高昂。

虽然可以在逻辑层面上说,非成员函数可以与类一起打包并组成类的接口,但是如果方法的位置发生变化,开发人员仍需要进行一些代码更改,因此接口的实现在使用中并不是完全透明的。

Eiffel语言的一个好处是,没有参数的方法可以像变量一样访问,因此在这些元素之间进行切换是透明的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接