是否可以放弃简单类的getter和setter?

时间:2011-06-29 23:41:19

标签: c++ oop encapsulation getter-setter member-variables

我正在制作一个非常简单的类来表示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());

这是良好做法的正常例外吗?我的代码的(假设的)未来维护者是否会追捕我并打击我?

6 个答案:

答案 0 :(得分:16)

使用getter和setter背后的想法是能够执行除 设置值之外的其他行为。建议采用这种做法,因为您可能希望将很多内容改装到您的班级中。

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

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

如果您认为您的代码永远不会,使用或要求上述任何一种,那么按原则编写getter和setter肯定是 not 良好做法。它只会导致代码膨胀。

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

答案 1 :(得分:3)

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

如果您确实希望将数据成员设为私有,但仍然可以对其进行完全访问,那么只需将一个访问者函数重载为T & access()一次,而将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).

答案 2 :(得分:2)

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

更深入地了解细节,现有答案中可能缺少/低估这些维度。封装的好处随着:

而增加
  • 重新编译/链接依赖:大量应用程序使用的低级库代码,这些应用程序可能非常耗时和/或难以重新编译和重新部署
    • 如果实施是脱节的(这可能需要pImpl惯用语和性能妥协)通常会更容易,所以你只需重新链接,如果你可以部署新的共享库并简单地反弹应用程序,那就更容易了
    • 通过对比,如果对象仅用于特定翻译单元的“非extern”实现,那么封装的好处将大大减少
  • 接口稳定性,尽管实现波动性:代码,其中实现更具实验性/易变性,但API要求已被充分理解
    • 请注意,在使用typedef作为其类型时,可以直接访问成员变量,这样可以替换代理对象并支持相同的客户端代码使用,同时调用不同的实现

答案 3 :(得分:0)

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

  1. 可以有任何可能的值,例如int;
  2. 出于性能原因,在操作它的值时应该像内置类型一样操作。
  3. 但是,如果你需要的不仅仅是“只是一个3D矢量”的类型,那么你应该把它包装在另一个类中,作为私有成员,然后通过成员函数和其他特性成员公开x,y和z功能

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

  

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

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

  

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

模拟论点 - 见上文。

  

这是良好做法的正常例外吗?

我不这么认为。您的问题意味着验证存在,但它不值得实施/支持,因为您已选择在您的实施中使用template,而不是专门适用于您选择的语言功能。通过这种方法,界面似乎只是部分支持 - 那些缺失的实现只会污染客户端&#39;的实施方式。