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
source to share
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; -)
source to share
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.
source to share
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.
source to share
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.
source to share
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.
source to share