我正在制作一个非常简单的类来表示3D空间中的位置。
目前,我只是让用户直接访问和修改单个X
,Y
和Z
值。换句话说,它们是公共成员变量。
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());
这是良好做法的正常例外吗?我的代码的(假设的)未来维护者是否会追捕我并打击我?
答案 0 :(得分:16)
使用getter和setter背后的想法是能够执行除 设置值之外的其他行为。建议采用这种做法,因为您可能希望将很多内容改装到您的班级中。
使用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
更深入地了解细节,现有答案中可能缺少/低估这些维度。封装的好处随着:
而增加extern
”实现,那么封装的好处将大大减少typedef
作为其类型时,可以直接访问成员变量,这样可以替换代理对象并支持相同的客户端代码使用,同时调用不同的实现答案 3 :(得分:0)
这种众所周知的结构是可以的:
但是,如果你需要的不仅仅是“只是一个3D矢量”的类型,那么你应该把它包装在另一个类中,作为私有成员,然后通过成员函数和其他特性成员公开x,y和z功能
答案 4 :(得分:0)
如果你做了一些非常简单的事情,你的解决方案就可以了。
如果您后来意识到球面坐标系中的计算更容易或更快(并且您需要性能),那么您可以依靠这一点。
答案 5 :(得分:0)
这背后的原因是,因为NumericType是一个模板参数,所以我不能依靠有一个体面的方法来检查理智值。 (我怎么知道用户不希望某个职位用负值表示?)
语言和编译器很好地支持这种情况(通过专业化)。
因此,添加getter或setter以使接口复杂化没有意义,因为简洁直接访问应该受到青睐。
模拟论点 - 见上文。
这是良好做法的正常例外吗?
我不这么认为。您的问题意味着验证应存在,但它不值得实施/支持,因为您已选择在您的实施中使用template
,而不是专门适用于您选择的语言功能。通过这种方法,界面似乎只是部分支持 - 那些缺失的实现只会污染客户端&#39;的实施方式。