C ++ Getter / Setter (Alternatives?)

Ok, almost everywhere I've read I've read that getters / setters are "evil". Now, as a programmer who uses getters / setters a lot in PHP / C #, I don't see how they are alive. I read that they destroy encapsulation etc., however, here's a simple example.

class Armor{

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

      

Now, let's say getters and setters are "evil". How should you change the member variable after initialization.

Example:

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?

      

Let's say the above is not for any reason. What if my game limits armor values โ€‹โ€‹from 1 to 500. (No armor can have a piece that has more than 500 armor or less than 1 armor).

Now my implementation becomes

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

      

So how else would I impose this constraint without using getters / setters? How else can I change the property without using getters / setters? Should armorValue be a public member variable in case 1, and getters / setters used in case 2?

Curious. God damn guys

+3


source to share


8 answers


You misunderstood something. Not using getters / setters breaks encapsulation and exposes implementation details and can be considered "evil" for some definition of evil.

I guess they can be considered evil in the sense that without proper IDE / editor support they are a bit of a tedioid to write in C ++ ...



One C ++ mistake is creating a non-const getter reference that allows modification as well. Same as returning a pointer to internal data and locks that part of internal implementation, and really no better than making the field public.

Edit: Based on the comments and other answers, what you've heard probably refers to the fact that there is always a non-personal getter and setter created for each field. But I, too, would not call this evil, just stupid; -)

+12


source


To be slightly opposite: yes, getters and setters (like accessors and mutators) are mostly evil.

The evil is not so much IMO, as much from "breaking encapsulation" as from simply defining a variable of one type (eg int

) when it really isn't that type at all. Looking at your example, you are calling Armor a int

, but it really isn't. While this is undoubtedly an integer, it is certainly not the int

one that (among other things) defines the range. Although your type is an integer, it never planned to maintain the same range as it int

does in general. If you want to Armor

have a type integer from 1 to 500

, define a type for direct representation and define Armor

as an instance of that type. In this case, since the invariant you want to apply is defined as part of the type itself, you don't need to bind an installer to it to try to enforce it.

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;
    }
};

      

At the same time, the definition of some armor with a range of 1..500 becomes completely trivial:

bounded<int> armor(1, 500);

      



Depending on the situation, you might define (for example) a type saturating

where trying to assign an out-of-range value would be fine, but the value that is actually assigned will just be the closest value that is within the range.

saturating<int> armor(1, 500);

armor = 1000;

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

      

Of course, what I gave above is also slightly bare bones. It will probably be convenient for your armor type to maintain -=

(and possibly +=

) so that the attack ends with something like x.armor -= 10;

.

Bottom line: The main problem (or at least "one") with getters and setters is that they usually indicate that you've defined a variable as one type when you really need some other type, which is turned out to be kind of similar in several respects.

It is now true that some languages โ€‹โ€‹(like Java) cannot provide the programmer with the tools needed to write such code. Here, I trust your use of the C ++ tag to indicate that you really want to write C ++. C ++ provides you with the tools you need and (at least IMO) your code will be better for you to efficiently use the tools it provides, so your type enforces the required semantic constraints while maintaining clean, natural, readable syntax.

+7


source


In short: they are not evil.

Itโ€™s nothing wrong with them if they donโ€™t leak out of the inner representation. I see no problem here.

+3


source


Your receiver / setter looks fine.

An alternative to getter / setters is to include member variables. More precisely, group variables into a structure with no member functions. And work on this structure in your class

+1


source


Providing member access shortens encapsulation, but is sometimes necessary. And the best way to do it is with getters and setters. Some people implement them when such access is not needed, simply because they can and it is a habit.

+1


source


Getters are evil whenever:

  • They access directly the data members of the class
  • When you need to add a new receiver every time you add data to the class
  • Behavior of data differently in each recipient

Good recipients, therefore, will do the following:

  • They redirect the request to some other object or collect data from multiple locations.
  • You can get large amounts of data using just one getter
  • All the data you retrieve is treated the same.

Nethers, on the other hand, are always evil.

+1


source


A common criticism of get / set functions is that they can abuse client code to perform operations that should logically be encapsulated in a class. For example, let's say a client wants to "polish" their armor and decides that the effect is to increase the "value" by 20 so that they do their little loot and ask things and be happy. Then someone else client code elsewhere decides that the rusty armor should lower the value by 30, and they do their bit. Meanwhile, a dozen other places in the client code also allow buffing and rusting effects on armor - as well as saying "reinforce" and "hack" and implement them directly. There is no central control over this ... the maintenance armor class has no ability to do things like:

  • the effects of rust, polish, reinforcement and cracks are applied no more than once per piece of armor

  • adjust the number added or subtracted from the value for certain boolean effects

  • decide that the new "leather" type of armor cannot rust and ignore the client's attempts to do so.

On the other hand, if the first customer who wanted to make a gun rusty couldn't do it through the interface, they went to the maintainer armor class and said "hey, give me the function to do this", then other people could start using the operation. " rust "on a logical level, and if it later became useful to do what I am talking about above, they can be easily implemented and centralized in the armor class (for example, having a separate logical say whether the armor was rusty or a separate variable registering the effect of rust ).

So the thing with get / set functions is that they frustrate the natural evolution of the logic functionality API, instead distributing logic throughout the client code, leading to unattainable mess at extremes.

+1


source


how could i enforce this constraint without using getters / setters? How else can I change the property without using getters / setters?

You can check what you read from this variable and if its value is out of range, use a predefined value instead.

You can also resort to dirty hacks, for example, protect the memory under a variable from writing, catch write attempts and disallow / ignore those with invalid values. This would be cumbersome to implement and expensive to implement. However, it can be useful for debugging.

0


source







All Articles