C++ Getter/Setter(替代品?)

Sco*_*niz 7 c++

好吧,就在我读到的每个地方,我都读到了吸气剂/孵化器是"邪恶的".现在,作为一个经常在PHP/C#中使用getter/setter的程序员,我看不出他们是如何活着的.我已经读过他们打破了封装等等,但是,这是一个简单的例子.

class Armor{

  int armorValue;
public:
  Armor();
  Armor(int); //int here represents armor value
  int GetArmorValue();
  void SetArmorValue(int);
};
Run Code Online (Sandbox Code Playgroud)

现在,让我们说吸气剂和制定者是"邪恶的".如何在初始化后更改成员变量?

例:

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?
Run Code Online (Sandbox Code Playgroud)

让我们说无论出于何种原因,上述情况都不合适.如果我的游戏将护甲值限制在1到500之间怎么办?(没有护甲可以拥有超过500护甲或少于1护甲的护甲).

现在我的实施成了

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

那么,如果不使用getter/setter,我将如何施加此限制?如果不使用getter/setter,我还能如何修改属性?armorValue应该是案例1中的公共成员变量,还是案例2中使用的getter/setter?

好奇.多谢你们

hyd*_*yde 15

你误会了什么.使用getter/setter会打破封装并暴露实现细节,并且对于某些邪恶定义可能被视为"邪恶".

我猜他们在某种意义上可以被认为是邪恶的,如果没有适当的IDE /编辑器支持,他们在某种程度上是用C++编写的tediois ...

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

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

  • 是的,从某种意义上说,当然没有吸气剂/孵化器的私人场地是"最好的".并且通常只需要吸气剂,没有固定器.要问的问题是,如果你改变了内部实现,并且字段消失了,那么getter或setter是否仍然有意义?如果没有,那么它不应该是公共API的一部分.也可以根据它的作用命名getter,而不是在初始实现中得到什么字段. (2认同)

Jer*_*fin 10

稍微逆势:是的,getter和setter(又名存取函数)大多是邪恶的.

这里的邪恶不是IMO,而是来自"打破封装",就像简单地将变量定义为一种类型(例如int),当它真的不是那种类型时.看看你的例子,你正在给Armor打电话int,但事实并非如此.虽然它无疑是一个整数,但它肯定不是一个int,其中(除其他外)定义了一个范围.虽然你的类型是一个整数,但它从来没有打算支持相同的范围int.如果您想Armor成为一个类型integer from 1 to 500,请定义一个直接表示该类型的类型,并将其定义Armor为该类型的实例.在这种情况下,由于您要强制执行的不变量被定义为类型本身的一部分,因此您无需在其上添加setter来尝试强制执行它.

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

有了这个,定义一些范围为1..500的护甲变得完全无足轻重:

bounded<int> armor(1, 500);
Run Code Online (Sandbox Code Playgroud)

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

saturating<int> armor(1, 500);

armor = 1000;

std::cout << armor;  // prints "500"
Run Code Online (Sandbox Code Playgroud)

当然,我上面给出的内容也有点勉强.对于你的盔甲类型,它可能很方便支持-=(并且可能+=),因此攻击最终会像x.armor -= 10;.

底线:getter和setter的(或至少"一个")主要问题是它们通常指向你已经将变量定义为一种类型,当你真的想要一些其他类型的碰巧类似于几种方式.

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


Kar*_*ath 5

简而言之:它们不是邪恶的.

只要它们不泄漏内部表示,它们就没有错.我觉得这里没问题.