Getters and Setters are EvilUse of getters and setters is in opposition to the fundamentals of object oriented design: Data abstraction and encapsulation. Overuse of getters and setters will make your code less agile and maintainable in the long run. They ultimately expose the underlying implementation of your class, locking implementation details into the interface of the class.Imagine your 'std::string Foo::bar' field needs to change from a std::string to another string class, that, say, is better optimized or supports a different character-set. You'll need to change the private data field, the getter, the setter, and all the client code of this class that calls these getters and setters.Rather than design your classes to "provide data" and "receive data", design them to "perform operations" or "providide services". Ask yourself why you're writing a "GetBar" function. What are you doing with that data? Perhaps you're displaying that data on or doing some processing on it. Is this process better exposed as a method of Foo?This not to say that getters and setters don't have their purpose. In C# I believe the fundamental reason for their use is to interface with the Visual Studio GUI-design IDE, but if you find yourself writing them in C++, it's probably best to take a step back, look at your design, and see if something is missing.
Code:
// A class that represents a user's bank account
classAccount{
private:
int balance_;// in cents, lets say
public:
constint&GetBalance(){return balance_;}
voidSetBalance(int b){ balance_ = b;}
};
classDeposit{
private:
int ammount_;
public:
constint&GetAmount(){return ammount_;}
voidSetAmmount(int a){ _balance = a;}
};
voidDoStuffWithAccount(){
Account a;
// print account balance
int balance = a.GetBalance();
std::cout << balance;
// deposit some money into account
Deposit d(10000);
a.SetBalance( a.GetBalance()+ d.GetValue());
}
t doesn't take very long to see that this is very poorly designed.
- Integers are an awful currency datatype
- A Deposit should be a function of the Account
The getters and setters make it more difficult to fix the problems, since the client code DoStuffWithAccount is now bound to the data-type we used to implement the account balance.So, lets make a pass on this code and see what we can improve
Code:
// A class that represents a user's bank account
classAccount{
private:
float balance_;
public:
voidDeposit(float b){ balance_ += b;}
voidWithdraw(float w){ balance_ -= w;}
voidDisplayDeposit(std::ostream &o){ o << balance_;}
};
voidDoStuffWithAccount(){
Account a;
// print account balance
a.DisplayBalance(std::cout);
// deposit some money into account
float depositAmt =1000.00;
a.Deposit(depositAmt);
a.DisplayBalance(std::cout);
}
The 'float' is a step in the right direction. Granted, you could have changed the internal type to 'float' and still supported the getter/setter idiom:
Code:
classAccount{
private:
// int balance_; // old implementation
float balance_;
public:
// support the old interface
constint&GetBalance(){return(int) balance_;}
voidSetBalance(int b){ balance_ = b;}
// provide a new interface for the float type
constfloat&GetBalance(){return balance_;}// not legal! how to expose getter for float as well as int??
voidSetBalance(float b){ balance_ = b;}
};
but it doesn't take long to realize that the getter/setter arrangement is doubling your workload and complicating matters as you need to support both the code that used ints and the new code that will use floats. The Deposit function makes it a bit easier to expand the range of types for depositing.An Account-like class is probably not the best example, since "getting" the account balance is a natural operation for an Account. The overall point, though, is that you must be careful with getters and setters. Do not get into the habit of writing getters and setters for every data-member. It is quite easy to expose and lock yourself into an implementation if you are not careful.