为了简单的类而放弃 getter 和 setter 可以吗?

发布于 2024-11-17 22:47:20 字数 586 浏览 4 评论 0原文

我正在创建一个非常简单的类来表示 3D 空间中的位置。

目前,我只是让用户直接访问和修改各个 XYZ 值。换句话说,它们是公共成员变量。

template <typename NumericType = double>
struct Position
{
    NumericType X, Y, Z;

    // Constructors, operators and stuff...
};

这背后的原因是,因为 NumericType 是一个模板参数,所以我不能依赖有一种体面的方法来检查值的完整性。 (我怎么知道用户不希望用负值表示位置?)因此,添加 getter 或 setter 来使界面复杂化是没有意义的,而且直接访问应该因其简洁性而受到青睐。

Pos.X = Pos.Y + Pos.Z; // Versus...
Pos.SetX(Pos.GetY() + Pos.GetZ());

这是良好实践的例外吗?我的代码的(假设的)未来维护者会追捕我并打我的脸吗?

I'm making a very simple class to represent positions in 3D space.

Currently, I'm just letting the user access and modify the individual X, Y and Z values directly. In other words, they're public member variables.

template <typename NumericType = double>
struct Position
{
    NumericType X, Y, Z;

    // Constructors, operators and stuff...
};

The reasoning behind this is that, because NumericType is a template parameter, I can't rely on there being a decent way to check values for sanity. (How do I know the user won't want a position to be represented with negative values?) Therefore, there's no point in adding getters or setters to complicate the interface, and direct access should be favored for its brevity.

Pos.X = Pos.Y + Pos.Z; // Versus...
Pos.SetX(Pos.GetY() + Pos.GetZ());

Is this an okay exception to good practice? Will a (hypothetical) future maintainer of my code hunt me down and punch me in the face?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

奢欲 2024-11-24 22:47:20

使用 getter 和 setter 背后的想法是能够执行其他行为,而不仅仅是设置值。推荐这种做法是因为您可能想要在课堂上改进很多东西。

使用 setter 的常见原因(可能还有更多):

  • 验证:并非变量类型允许的所有值都对成员有效:分配之前需要验证。
  • 不变量:依赖字段可能需要调整(例如,调整数组大小可能需要重新分配,而不仅仅是存储新大小)。
  • 挂钩:在赋值之前/之后需要执行额外的工作,例如触发通知(例如在值上注册观察者/侦听器)。
  • 表示:字段不以“发布”的格式存储为 getter 和 setter。该字段甚至可能不存储在对象本身中;该值可能会转发给其他一些内部成员或存储在单独的组件中。

如果您认为您的代码永远不会使用或需要上述任何内容,那么按原则编写 getter 和 setter 绝对不是一个好习惯。它只会导致代码膨胀。

编辑:与普遍的看法相反,使用 getter 和 setter 不太可能帮助您更改类的内部表示,除非这些更改很小。尤其是个人成员设置者的存在,使得这种改变变得非常困难。

The idea behind using getters and setters is to be able to perform other behavior than just setting a value. This practice is recommended because there are a multitude of things you might want to retrofit into your class.

Common reasons to use a setter (there are probably more):

  • Validation: not all values allowed by the type of the variable are valid for the member: validation is required before assignment.
  • Invariants: dependent fields might need to be adjusted (e.g. re-sizing an array might require re-allocation, not just storing the new size).
  • Hooks: there is extra work to perform before/after assignment, such as triggering notifications (e.g. observers/listeners are registered on the value).
  • Representation: the field is not stored in the format "published" as getters and setters. The field might not even stored in the object itself; the value might be forwarded to some other internal member or stored in separate components.

If you think your code will never, ever use or require any of the above, then writing getters and setters by principle is definitely not good practice. It just results in code bloat.

Edit: contrarily to popular belief, using getters and setters is unlikely to help you in changing the internal representation of the class unless these changes are minor. The presence of setters for individual members, in particular, makes this change very difficult.

无法回应 2024-11-24 22:47:20

如果 getter 和 setter 获取/设置您可能以多种方式实现的抽象值,那么它们实际上只是一个重要的设计选择。但是,如果您的类如此简单并且数据成员如此基本以至于您需要直接公开它们,那么只需将它们公开即可!你会得到一个漂亮、便宜的聚合类型,没有任何装饰,而且它是完全自我记录的。

如果您确实想要将数据成员设为私有,但仍授予对其的完全访问权限,只需将单个访问器函数重载一次,如 T & access() 并一次作为 const T &访问()常量


编辑:在最近的一个项目中,我只是使用元组作为坐标,并具有全局访问器函数。也许这可能有用:

template <typename T>
inline T cX(const std::tuple<T,T,T> & t) { return std::get<0>(t); }

typedef std::tuple<double, double, double> coords;
//template <typename T> using coords = std::tuple<T,T,T>; // if I had GCC 4.8

coords c{1.2, -3.4, 5.6};

// Now we can access cX(c), cY(c), cZ(c).

Getters and setters are really only an important design choice if they get/set an abstract value that you may have implemented in any number of ways. But if your class is so straight-forward and the data members so fundamental that you need to expose them directly, then just make them public! You get a nice, cheap aggregate type without any frills and it's completely self-documenting.

If you really do want to make a data member private but still give full access to it, just make a single accessor function overloaded once as T & access() and once as const T & access() const.


Edit: In a recent project I simply used tuples for coordinates, with global accessor functions. Perhaps this could be useful:

template <typename T>
inline T cX(const std::tuple<T,T,T> & t) { return std::get<0>(t); }

typedef std::tuple<double, double, double> coords;
//template <typename T> using coords = std::tuple<T,T,T>; // if I had GCC 4.8

coords c{1.2, -3.4, 5.6};

// Now we can access cX(c), cY(c), cZ(c).
拧巴小姐 2024-11-24 22:47:20

我花了一段时间,但我跟踪了这​​个旧的 Stroustrup 采访,他自己讨论了暴露数据结构与封装类: http://www.artima.com/intv/goldilocks3.html

更深入地了解细节,现有答案中可能缺少/低估了某些维度。封装的好处随着以下方面的增加而增加:

  • 重新编译/链接依赖:大量应用程序使用的低级库代码,这些应用程序可能非常耗时和/或难以重新编译和重新部署
    • 如果实现不符合要求(这可能需要 pImpl 习惯用法和性能妥协),通常会更容易,因此您只需重新链接即可,如果您可以部署新的共享库并简单地弹回应用程序,情况会更容易
    • 相比之下,如果对象仅用于特定翻译单元的“非外部”实现,则封装的好处会大大减少
  • 尽管实现存在波动,但接口稳定性:实现所在的代码更具实验性/ 易失性,但 API 要求很好理解
    • 请注意,通过小心,可以在使用 typedef 作为成员变量的类型时直接访问成员变量,这样就可以替换代理对象并支持相同的客户端代码使用调用不同的实现

Took me a while, but I tracked this old Stroustrup interview down, where he discusses exposed-data structs versus encapsulated classes himself: http://www.artima.com/intv/goldilocks3.html

Getting more heavily into specifics, there's are dimensions to this that may be missing / understated in existing answers. The benefits of encapsulation increase with:

  • re-compilation/link dependency: low-level library code that is used by large numbers of applications, where those apps may be time-consuming and/or difficult to recompile and redeploy
    • it's usually easier if implementation was out-of-line (which may require pImpl idiom and performance compromises) so you only have to relink, and easier still if you can deploy new shared libraries and simply bounce the app
    • by way of contrast, there's massively less benefit from encapsulation if the object's only used in "non-extern" implementation of a specific translation unit
  • interface stability despite implementation volatility: code where the implementation is more experimental / volatile, but the API requirement is well understood
    • note that by being careful it may be possible to give direct access to member variables while using typedefs for their types, such that a proxy object can be substituted and support identical client-code usage while invoking different implementation
焚却相思 2024-11-24 22:47:20

如果你做了一些非常简单的事情,你的解决方案可能就很好。

如果您后来意识到球坐标系中的计算更容易或更快(并且您需要性能),那么您可以信赖这一点。

If you do some very easy stuff your solution could be just fine.

If you later realize that calculations in a spherical coordinate system are much easier or faster (and you need performance), you can count on that punch.

拥抱影子 2024-11-24 22:47:20

对于这样的众所周知的结构来说是可以的:

  1. 可以有任何可能的值,比如 int;
  2. 出于性能原因,在操作其值时应像内置类型一样操作。

但是,如果您需要的不仅仅是“只是 3D 向量”的类型,那么您应该将其包装在另一个类中,作为私有成员,然后通过成员函数和附加功能成员函数公开 x、y 和 z。

It is ok for such well known structure that :

  1. Can have any possible values, like an int;
  2. Should operate like a built-in type when manipulating it's value, for performance reasons.

However, if you need more than a type that "just is a 3D vector", then you should wrap it in another class, as private member, that would then expose x, y and z through member functions and additional features member functions.

纸短情长 2024-11-24 22:47:20

这背后的原因是,因为 NumericType 是一个模板参数,所以我不能依赖有一种体面的方法来检查值的完整性。 (我怎么知道用户不希望用负值表示位置?)

语言和编译器很好地支持这种情况(通过专门化)。

因此,添加 getter 或 setter 来使接口复杂化是没有意义的,直接访问应该因其简洁性而受到青睐。

没有实际意义的争论——见上文。

这是良好实践的例外吗?

我不认为是这样。您的问题意味着验证应该存在,但不值得实现/支持,因为您选择在实现中使用模板,并且不专门适合您的语言功能已经选择了。通过这种方法,接口似乎只得到部分支持——那些缺少的实现只会污染客户端的实现。

The reasoning behind this is that, because NumericType is a template parameter, I can't rely on there being a decent way to check values for sanity. (How do I know the user won't want a position to be represented with negative values?)

The language and compilers support this case well (via specialization).

Therefore, there's no point in adding getters or setters to complicate the interface, and direct access should be favored for its brevity.

Moot argument -- see above.

Is this an okay exception to good practice?

I don't think it is. Your question implies validation should exist, but it's not worth implementing/supporting because you've chosen to use a template in your implementation, and not specialize appropriate for the language feature you've chosen. By that approach, the interface only appears to be partially supported -- those missing implementations will just pollute clients' implementations.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文