“巅峰”封装 - 关于有效 C++ 的建议的问题
《Effective C++》第 23 条规定:优先选择非成员非友元函数而不是成员函数。
该项目的全部目的是鼓励封装以及包灵活性和功能可扩展性,但我的问题是您在采纳此建议方面走多远?
例如,您可以拥有您的类、您的私有数据成员,然后采用极简方法,将公共函数减少为仅用于您的私有数据成员的访问器和/或修改器。然后,每个其他函数都可以是非成员函数。
然而,您是否愿意以牺牲代码清晰度为代价来增加封装性,因为访问器和修改器遍布各处?这条线画在哪里?
Item 23 of Effective C++ states: Prefer non-member non-friend functions to member functions.
The whole purpose of the item was to encourage encapsulation, as well as package flexibility and functional extensibility, but my question is how far do you go when it comes to taking this advice?
For example, you could have your class, your private data members, and then take a minimalist approach by reducing public functions to only accessors and/or mutators for your private data members. Then, every single other function could be a non-member function.
However, would you be willing to increase encapsulation at the possible sacrifice of code clarity with accessors and mutators all over the place? Where is the line drawn?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
首先,并非所有人都同意这个建议。我想除了迈耶斯我没见过任何人(编辑:和 Herb Sutter)给出了这个建议,而且我只在 C++ 上下文中看到过它。例如,在 Java 或 C# 中创建“非成员非友元函数”实际上是不可能的,因为 Java 和 C# 没有自由函数,而 Ruby 开发人员(例如)更喜欢 "人性化接口" 有意创建成员函数来完成非成员可以做的同样的事情,只是为了让这些函数的调用者的生活更轻松。
即使您确实接受迈耶斯的建议,您也应该更喜欢非成员非友元函数而不是成员函数(我认为这是一个很好的建议,它确实帮助我更好地应用封装,以考虑甚至从其成员封装类的实现函数),这只是需要考虑的设计轴之一。
面向对象设计的关键概念是对象做某事。对象不仅仅是由其他代码执行操作的一组 setter 和 getter。相反,它应该附加行为——也就是说,它应该有做事情的方法——并且它应该封装它如何做这些事情的细节。如果您遵循这种面向对象的方法,那么像您一样将 Meyers 的建议推向极端会伤害封装而不是帮助它:您最终会通过 getter 和 setter 公开类的所有内部实现变量,而不是隐藏它们,以便只有类的方法(代表类负责做事的代码,这是您拥有一个类的唯一原因)可以访问它。
因此,要回答您的问题,即在多大程度上采纳迈耶斯的建议:如果可以使用类的公共接口将函数合理地实现为非友元非成员函数,则不要不必要将函数转换为成员函数,但不要为了避免让某些东西成为成员而暴露实现,从而破坏类的公共接口并违反其封装。并确保您平衡封装与其他问题和其他方法(包括,如果您的团队决定走这条路,成熟的人性化界面的优点和缺点)。
First, not everyone agrees with this advice. I don't think I've seen anyone but Meyers (edit: and Herb Sutter) give this advice, and I've only seen it given within the context of C++. For example, creating "non-member non-friend functions" in Java or C# isn't really possible, since Java and C# have no free functions, and Ruby developers (for example) prefer "humane interfaces" that intentionally create member functions that do the same thing non-members could, just to make life easier on those functions' callers.
And even if you do accept Meyers' advice, that you should prefer non-member non-friend functions to member functions (and I think it's good advice, it certainly helped me apply encapsulation better to think of encapsulating a class's implementation even from its member functions), that's only one axis of design to consider.
The key concept of object-oriented design is that objects do something. An object isn't simply a bag of setters and getters that other code does stuff to. Instead, it should have behavior attached - that is, it should have methods that do things - and it should encapsulate the details of how it does those things. If you follow this approach to OO, then carrying Meyers' advice to the extreme as you did hurts encapsulation rather than helping it: you end up exposing all of the class's internal implementation variables via getters and setters instead of hiding them so that only the class's methods (the code responsible for doing stuff on behalf of the class, which is the only reason you have a class to begin with) can get to it.
So to answer your question of how far to take Meyers' advice: Don't needlessly turn functions into member functions if they could reasonably be implemented as non-friend non-member functions using a class's public interface, but don't damage a class's public interface and violate its encapsulation by exposing implementation just to avoid making something a member. And make sure you balance encapsulation against other concerns and other approaches (including, if your team decides to go that route, the pros and cons of a full-blown humane interface).
退后一步,考虑一下该类的目的:它在做什么一项工作?它必须确保哪些类不变量才能最佳地完成这项工作?子类化和重写在类的目的中扮演什么角色?
将所有内容都转换为访问器和修改器绝对是不合适的:这几乎会脱离 OOP 根源的状态和行为的结合,或者掩盖问题的合理框架中的行为是关于在这种“假装的”修改器和访问器的面纱下获取或设置属性。
仅具有访问器和修改器的类是一种特殊情况 - 也许比传统的 C 类
struct
更进一步,能够保留一些不变量,但“只是勉强” ;-)。良好的 OOP 设计中的大多数类都会有行为——尽管我很喜欢泛型编程,但使用 C++ 的原因之一是它强大的多种范式组合,其中 OOP 一定不能被删除!-)
Take a step back and consider the purpose of the class: what one job is it doing? What class invariants must it ensure to do that one job optimally? What role do subclassing and overriding play in the class's purpose?
It's definitely not appropriate to cast everything in terms of accessors and mutators: that would nearly divorce the conjunction of state and behavior that is at the root of OOP, or mask behavior which in no sensible framing of the problem is about getting or setting attributes under the veil of such "pretended" mutators and accessors.
Classes with just accessors and mutators are one special case -- maybe one step up from traditional C-kind
struct
s by being able to preserve some invariants, but "just barely";-).Most classes in a good OOP design will have behavior -- and much as I like generic programming, one reason to use C++ is its strong mix of multiple paradigms, among which OOP must not be expunged!-)
实际上,只为类的私有变量提供访问器和修改器实际上并不是极简主义的(或者在某种意义上可能是极简主义的,但不是“最相关”的意义上的),因为您现在正在呈现一个更通用的 与世界的接口。封装的想法是,类的接口应该尽可能受到限制,同时允许客户端代码完成工作。
遵循这种方法使得将来更容易更改底层实现,这首先是封装的要点。
Actually, providing only accessors and mutators to your class's private variables would in fact not be minimalist (or perhaps minimalist in one sense but not in the "most relevant" sense), as you are now presenting a more general interface to the world. The idea of encapsulation is that your class's interface should be as restricted as possible while allowing client code to get the job done.
Following this approach makes is easier to change the underlying implementation in the future, which is the point of encapsulation in the first place.
一般来说,访问器和修改器背后的想法是需要以某种方式保护变量。如果变量包含应该仅由一个实体更新的敏感信息,您可以在您的变异器中包含该检查。简单的事实是,大多数访问器和修改器只是一种形式(以至于 C# 现在具有自动属性)并且不是必需的。最重要的是,运用你的判断力。如果需要限制访问,则添加一个修改器。如果谁获取/设置变量并不重要,则不需要访问器和修改器。
In general the idea behind the accessors an mutators is that the variable needs to be guarded in some way. if the variable has sensitive information that is supposed to be updated by only one entity, you can include that check in your mutator. The simple fact is that most accessors and mutators are simply a formality (so much so that C# now has automatic properties) and are not necessary. The bottom line is, use your judgment. if it needs restricted access, then add a mutator. If it doesnt matter who gets / sets the variable, the accessor and mutator aren't needed.
研究如何将事物打包成合适大小的单元是一门主观艺术。
我在将非成员非朋友化时遇到的问题之一是,如果内部结构发生变化,需要添加新的公共接口来支持现有的非成员,或者非成员现在被视为朋友,那么您就显示出比之前设计的更紧密的联系。现在做出任何改变都可能变得昂贵。
虽然在逻辑层面上可以说非成员函数可以与类打包并构成类的接口,但如果方法的位置发生变化,开发人员将需要进行一些代码更改,因此呃,该接口的使用并不透明。
Eiffel 的优点之一是,不带参数的方法的访问方式与变量相同,因此这些元素之间的更改是透明的。
Working on how to package things into right sized units is a subjective art.
One of the issues I have with making something non-member non-friend is that if a change in internal structure requires that new public interfaces be added to support existing non-members or that the non-member now be acknowledged as a friend you are showing a tighter linkage than was previously devised. It may now become costly to make any change.
While it can be said at a logical level a non-member function can be packaged with the class and make up the class' interface, the developer will need to make some code change if the location of a method changes, and so the implementation of the interface is not, errr, transparent in it's use.
One of those nice things about Eiffel, a method with no parameters is accessed in the same way as a variable, and so changing between those elements is transparent.