是否应隐藏所有数据成员?

时间:2012-10-14 13:06:47

标签: c++ oop

我知道OOP的'数据隐藏'概念,但在真正的开发中,当规范发生变化时,它总是会受到挑战。

例如:

class role
{
    std::string name;
    int level;
    public:
        const std::string& get_name() { return name; }
        void set_name(const std::string& value) { name = value; }
        void set_level(int value) { level = value; }
        int get_level() const { return level; }
}

当然,这段代码没有任何问题。但我认为namelevel根本没有封装。

我的意见如下:

  1. 用户可以通过setter功能修改数据。
  2. setter / getter 公开这些数据成员的数据类型。如果这些数据成员必须更改其类型,则setter / getter函数成员也必须更改其接口。
  3. 如果我需要level成员的其他操作,添加更多操作成员(e.q add_level(int value)sub_level(int value)等)功能是唯一的方法。
  4. 只有一件事情很好。如果get/set必须为它们添加一些判断,那么这些接口可以很好地工作。
  5. 那么,我应该封装哪种数据成员?我无法预测这些数据成员的规模和使用情况。如果我直接公开它们,对它们的操作将完全简单,清晰并且有意义。如果我封装它们,我会为它们创建许多操作成员,如果有一天它们的类型由spec(int - > class,class - > int)更改,我的操作成员必须更改其接口或直接终止它们(因为我会一次性将它们送到公共区域!)。

4 个答案:

答案 0 :(得分:3)

我一直在阅读许多不同的语言,特别是函数式语言,我慢慢地质疑 setters 的想法。

假设我有一个班级Person,这就是几年前我会写的:

class Person {
public:
    std::string const& name() const { return _name; }
    void name(std::string const& n) { _name = n; }

    unsigned age() const { return _age; }
    void age(unsigned a) { _age = a; }

private:
    std::string _name;
    unsigned _age;
};

你会注意到它与你自己的班级有多相似。

  • Person的名称会在对象的生命周期内发生变化吗?
  • Person的年龄会在对象的生命周期内发生变化吗?我们可以做些什么来避免这种情况?
  • 我对_name的类型感到束缚:改变它会打破name()吸气剂......

现在,另一种实现,就是我今天要写的:

class Person {
public:
    Person(std::string name, Time birth): _name(name), _birth(birth) {}

    std::string name() const { return _name; }

    Duration age(Time now) { return now - _birth; }

private:
    std::string _name;
    Time _birth;
};

此类不再具有任何setter 。这个类不再返回其内部的任何句柄(因此,我将支付副本的价格,无论如何可能不会太多)。

然而,最值得注意的是我改变了我记忆信息的方式:age是从出生日期和当前日期得出的波动值。因此,为什么要记住副产品而不是源?

我想改变价值?良好:

Time const now = Time::Now();
person = Person("John R. Smith", now - person.age(now));

效果很好。至少我只写了一次不变量(在构造函数中)。

显然,这并不一定适用于所有地方。如果你的班级只有几个领域虽然运作良好;当你的班级获得更多的领域时,也许是时候把它们中的一些提取出来了。

答案 1 :(得分:1)

这个想法是该类定义了一个接口,它是作者与使用它的任何开发者之间的契约。接口应尽可能保持不变,因为对接口的任何更改都可能需要更改使用它的代码。

编写setter和getter意味着您可以验证对私有成员的访问,并且您可以更改底层实现(例如,通过更改内部数据类型),而不会影响任何客户端代码。

例如,假设您已经编写了time类。您的二传手可以阻止来电者在hoursminutesseconds成员中存储无效值。并且假设从实时时钟中检索值。您的getter可以查询RTC并返回错误代码,如果它不可访问或损坏。

作为另一个例子,假设您已经定义了一个返回32位整数的getter。您稍后会发现可以通过更改实现以使用64位整数来修复的不准确性。 (也许它正在进行某种计算,将其四舍五入到32位。)getter封装了实现,因此您可以自由地更新它以在内部使用64位但仍返回(现在更准确)32位值,因此你的新课程是旧课程的替代品。

答案 2 :(得分:1)

Getters和setter通常仅在您执行的操作时才有用,而不仅仅是分配成员变量。

class A {
public:
  int get_value () const;
  void set_value (int v);

private:
  int _value;
};

如果您1)从不打算改变实现(基本上您不是针对接口进行编码),上面并没有真正为您买任何东西,2)没有要执行的验证。

如果您的类由不共享不变量的数据组成(基本上该类只收集不同的值,但成员之间没有规则),您可以争论getter和setter是否有用。

例如,在Date类的情况下,您应该使用setter和getter,因为成员(日,月和年)共享一个不变量(例如,当月份增加到12以上时,年份也应该增加)。

但是在Point类中,X和Y不共享不变量,你可以跳过getter和setter,除非你真的想让它成为一个接口并使用多态。

另外我不同意OOP是有挑战性的,因为规格会发生变化(他们总是会这样做),但正确使用OOP是一种技术(与其他人结合使用)可以帮助您应对变化并将程序的某些部分相互隔离这样改变不会渗透整个代码库。我不会说你可以遵循任何简单的规则,这样才有效,而这是一个经验问题。

答案 3 :(得分:0)

  1. 使用set / get修改OUTSIDE类中的值(比如说来自另一个类)。
  2. 隐藏不需要在课堂外暴露的功能(私有)。例如,我有一个类,它具有使用各种方法计算Integration的函数。但是所有这些算法都依赖于一个名为sumIt()的函数,该函数不应该由其他类显式使用。
  3. 如果你想避免每次都手动编写自己的getter / setter,你可以使用模板轻松扩展C ++以允许“属性”像修饰符。 这取决于您的课程/应用程序的设计。