3

我知道 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成员进行其他操作,则添加更多操作成员(eqadd_level(int value)sub_level(int value))功能是唯一的方法。
  4. 只有一件事是好的。如果get/set一定要给它们加上一些判断,这些接口都可以正常工作。

那么,我应该封装什么样的数据成员呢?我无法预测这些数据成员的规模和使用情况。如果我直接暴露它们,对它们的操作将完全简单、清晰、有意义。如果我封装它们,我会为它们做很多操作成员,如果有一天它们的类型被规范改变(int -> class,class -> int),我的操作成员必须改变它们的接口或直接将它们全部杀死(因为我将它们发送到公共区域,一劳永逸!)。

4

4 回答 4

3

我一直在阅读许多不同的语言,尤其是函数式语言,我慢慢开始质疑setter的想法。

假设我有一个 class 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));

工作得很好。至少我只写一次我的不变量(在构造函数中)。

显然,这不一定适用于所有地方。如果您的班级只有几个字段,但效果很好;当您的班级获得更多字段时,也许是时候将其中一些提取到自己的班级中了。

于 2012-10-14T13:58:05.977 回答
1

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

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

例如,假设您编写了一个time类。您的设置器可以防止调用者在 、 和 成员中存储hours无效minutesseconds。并假设这些值是从实时时钟中检索的。您的 getter 可以查询 RTC 并在无法访问或损坏时返回错误代码。

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

于 2012-10-14T13:20:28.000 回答
1

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

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

private:
  int _value;
};

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

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

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

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

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

于 2012-10-14T13:46:50.807 回答
0
  1. 使用 set/get 修改类外部的值(比如来自另一个类)。
  2. 隐藏不需要在类外公开的函数(带有私有)。例如,我有一个类,它具有使用各种方法计算积分的功能。但是所有这些算法都依赖于一个名为 sumIt() 的函数,其他类不应明确使用该函数。
  3. 如果您想避免每次都手动编写自己的 getter/setter,您可以使用模板轻松扩展 C++ 以允许“属性”之类的修饰符。这取决于您的类/应用程序的设计。
于 2012-10-14T13:18:53.223 回答