20

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

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

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());

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

4

6 回答 6

16

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

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

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

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

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

于 2011-06-29T23:48:20.947 回答
3

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

如果您确实想将数据成员设为私有但仍对其进行完全访问,只需将单个访问器函数重载一次 asT & access()和一次 as const T & access() const


编辑:在最近的一个项目中,我只是将元组用于坐标,并带有全局访问器功能。也许这可能有用:

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).
于 2011-06-29T23:58:12.333 回答
2

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

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

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

这种众所周知的结构是可以的:

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

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

于 2011-06-29T23:46:43.963 回答
0

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

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

于 2011-06-29T23:47:00.993 回答
0

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

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

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

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

这是良好实践的一个好的例外吗?

我不认为它是。您的问题暗示应该存在验证,但它不值得实施/支持,因为您选择template在您的实施中使用 a ,而不是专门适用于您选择的语言功能。通过这种方法,接口似乎只得到部分支持——那些缺失的实现只会污染客户端的实现。

于 2012-04-22T05:42:27.630 回答