8

好的,几乎在我阅读的所有地方,我都读到 getter/setter 是“邪恶的”。现在,作为一名经常在 PHP/C# 中使用 getter/setter 的程序员,我看不出它们是如何存活的。我读过它们破坏了封装等,但是,这是一个简单的例子。

class Armor{

  int armorValue;
public:
  Armor();
  Armor(int); //int here represents armor value
  int GetArmorValue();
  void SetArmorValue(int);
};

现在,让我们说 getter 和 setter 是“邪恶的”。您应该如何在初始化后更改成员变量。

例子:

Armor arm=Armor(128); //armor with 128 armor value

//for some reason I would like to change this armor value
arm.SetArmorValue(55); //if i do not use getters / setters how is this possible?

可以说,无论出于何种原因,上述情况都不好。如果我的游戏将护甲值限制在 1 到 500 之间怎么办。(没有盔甲可以有一件盔甲超过 500 或少于 1 的盔甲)。

现在我的实现变成了

void Armor::SetArmor(int tArmValue){
      if (tArmValue>=1 && tArmValue<=500)
         armorValue=tArmValue;
      else
         armorValue=1;
}

那么,如果不使用 getter/setter,我还能如何施加这个限制呢?如果不使用 getter/setter,我还能如何修改属性?armourValue 是否应该只是案例 1 中的公共成员变量,以及案例 2 中使用的 getter/setter?

好奇的。多谢你们

4

8 回答 8

17

你误解了一些东西。使用 getter/setter 会破坏封装并暴露实现细节,并且对于某些邪恶的定义可以被认为是“邪恶的”。

我想它们在某种意义上可以被认为是邪恶的,如果没有适当的 IDE/编辑器支持,用 C++ 编写它们有点乏味......

C++ 的一个陷阱是创建非常量引用 getter,它也允许修改。这与返回指向内部数据的指针相同,并将锁定内部实现的那部分,并且实际上并不比公开字段更好。

编辑:根据评论和其他答案,您听到的可能是指始终为每个字段 创建非私有 getter 和 setter 。但我也不会称之为邪恶,只是愚蠢;-)

于 2013-01-26T18:03:28.963 回答
11

稍微逆向:是的,getter 和 setter(又名 accessors 和 mutators)大多邪恶的。

IMO,这里的邪恶不在于“破坏封装”,而在于简单地将变量定义为一种类型(例如,int),而实际上它根本不是那种类型。查看您的示例,您将 Armor an 称为int,但实际上并非如此。虽然它无疑是一个整数,但它肯定不是一个int,它(除其他外)定义了一个范围。虽然您的类型是整数,但它根本不打算支持与 a 相同的范围int。如果您想Armor成为 type integer from 1 to 500,请定义一个类型来直接表示它,并将其定义Armor为该类型的实例。在这种情况下,由于您要强制执行的不变量被定义为类型本身的一部分,因此您不需要

template <class T, class less=std::less<T> >
class bounded {
    const T lower_, upper_;
    T val_;

    bool check(T const &value) {
        return less()(value, lower_) || less()(upper_, value);
    }

    void assign(T const &value) {
        if (check(value))
            throw std::domain_error("Out of Range");
        val_ = value;
    }

public:
    bounded(T const &lower, T const &upper) 
        : lower_(lower), upper_(upper) {}

    bounded(bounded const &init) 
        : lower_(init.lower), upper_(init.upper), val_(init.val_)
    { }

    bounded &operator=(T const &v) { assign(v);  return *this; }

    operator T() const { return val_; }

    friend std::istream &operator>>(std::istream &is, bounded &b) {
        T temp;
        is >> temp;

        if (b.check(temp))
            is.setstate(std::ios::failbit);
        else
            b.val_ = temp;
        return is;
    }
};

有了这个,定义一些范围为 1..500 的盔甲变得非常简单:

bounded<int> armor(1, 500);

根据情况,您可能更喜欢定义(例如)一个saturating类型,尝试分配超出范围的值是可以的,实际分配的值将只是范围内最接近的值。

saturating<int> armor(1, 500);

armor = 1000;

std::cout << armor;  // prints "500"

当然,我上面给出的也有点简单。-=对于您的装甲类型,支持(并且可能)可能会很方便,+=因此攻击最终会像x.armor -= 10;.

底线:getter 和 setter 的(或至少“一个”)主要问题是,它们通常指向您已将变量定义为一种类型,而您确实希望其他类型恰好在几种方法。

现在,确实有些语言(例如Java)未能为程序员提供编写此类代码所需的工具。在这里,我相信您使用 C++ 标签来表明您确实想编写 C++。C++ 确实为您提供了必要的工具,并且(至少在 IMO)您的代码将更好地利用它提供的工具,以便您的类型强制执行所需的语义约束,同时仍然使用干净、自然、可读的语法。

于 2013-01-26T19:02:34.103 回答
5

简而言之:它们并不邪恶。

只要它们不泄露内部表示,它们就没有问题。我认为这里没有问题。

于 2013-01-26T18:03:41.620 回答
3

对 get/set 函数的一个常见批评是它们可能被客户端代码滥用来执行逻辑上应该封装在类中的操作。例如,假设客户想要“抛光”他们的盔甲,并决定效果是“价值”增加 20,所以他们做他们的小获取和设置并且很高兴。然后其他地方的其他客户端代码决定生锈的盔甲应该将值降低 30,他们会尽自己的一份力量。同时,客户端代码中的其他十几个地方也允许对装甲进行抛光和生锈效果 - 以及说“加固”和“开裂”,并直接实施它们。对此没有中央控制... Armor 类的维护者没有能力做以下事情:

  • 每件盔甲最多应用一次生锈、抛光、加固和开裂效果

  • 为特定的逻辑效果调整添加到值或从值中减去的数字

  • 确定新的“皮革”装甲类型不会生锈,并忽略客户试图让它生锈的尝试

另一方面,如果第一个客户端想要使盔甲生锈,但无法通过接口实现,他们会去找盔甲类的维护者说“嘿,给我一个函数来做这个”,然后其他人可以开始使用逻辑级别的“rust”操作,如果以后做我上面描述的那种事情变得有用,它们可以在 Armor 类中轻松集中地实现(例如,通过一个单独的布尔值来表示是否盔甲生锈了,或者是记录生锈效果的单独变量)。

因此,get/set 函数的问题在于它们阻碍了逻辑功能 API 的自然演变,而是将逻辑分布在整个客户端代码中,导致极端情况下无法维护的混乱局面。

于 2015-03-26T10:42:11.923 回答
1

你的 getter/setter 看起来不错。

getter/setter 的替代方法是将成员变量公开。更准确地说,将变量分组到没有成员函数的结构中。并在你的班级内对这个结构进行操作

于 2013-01-26T18:03:56.517 回答
1

授予成员访问权限会减少封装,但有时这是必要的。最好的方法是使用 getter 和 setter。有些人在不需要此类访问时实施它们,只是因为他们可以并且这是一种习惯。

于 2013-01-26T18:08:01.260 回答
1

吸气剂是邪恶的,只要:

  1. 他们直接访问类的数据成员
  2. 每次向类添加数据时都必须添加新的 getter
  3. 每个 getter 中的数据行为不同

因此,好的吸气剂会执行以下操作:

  1. 他们将请求转发给其他对象或从多个地方收集数据
  2. 只需一个 getter 即可获取大量数据
  3. 您获取的所有数据都以相同的方式处理

另一方面,二传手总是邪恶的。

于 2013-01-26T18:31:22.843 回答
0

在不使用 getter/setter 的情况下,我还能如何施加此限制?如果不使用 getter/setter,我还能如何修改属性?

您可以检查从变量中读取的内容,如果其值超出范围,请改用预定义值(如果可能)。

您还可以诉诸肮脏的技巧,例如保护变量下面的内存不被写入,捕获写入尝试以及禁止/忽略具有无效值的那些。这将是繁琐的实施和昂贵的执行。不过,它可能对调试有用。

于 2013-01-26T18:08:06.570 回答