While perusing the Internet, I stumbled on some question about getters and setters and why people are supposed to use them (which of course I can’t find the link to now, even with the help of the mighty Google). After reading the question, I realized something. Holy crap, a lot of getter/setter examples on the Internet aren’t that good. Most of the tutorials on getters and setters talk about variable visibility, and you don’t get any real useful discussion about how to use them properly outside of technical forums or discussion boards. So, I thought I’d write a quick little guide on the topic in part to increase the number of useful pages on the topic out there on the Internet, and thus make it easier to find a good reference on the matter.
The Principle
The idea behind getters/setters is that when you have private variables, you write methods that control how and when they’re accessed and modified. Getters/setters typically look something like:
private Object obj; public Object getObj() { return obj; } public void setObj(Object newObj) { obj = newObj; }
Just a dumb return and a dumb setter, neither of which demonstrate the principle of controlling access or how a private variable is manipulated.
The Bad Example
Here’s a quick version of the bad example of getters and setters. The basic underlying principle to this kind of example is that they’re busy explaining scope, which for my purposes I’ll limit to public and private, and getters and setters end up being an afterthought in response to private variables. This version of the bad example will be an online bank account.
Let’s take an online bank. Now, this bank knows your bank account balance, stored in the variable yourBalance, and my balance, stored in the variable ericBalance. With public variables, you could get your balance by just referring to bank.yourBalance. However, I could also see your balance by doing the same thing. What’s more, both of us can modify your balance by assigning bank.yourBalance to a new number. Since balances should only be viewable by the person who holds the account, we need to prevent this sort of behavior, so we’re going to make yourBalance and ericBalance private.
OK, you’re thinking, good idea. I don’t want Eric seeing or screwing with my bank balance, and I’m sure he doesn’t want me doing the same to his.
Of course, you still need to be able to access your balance, so we’ll write some methods to let us do this.
Great, that way I can still use my account.
We’ll write some to give you access to your account.
public double getYourBalance() { return yourBalance; } public void setYourBalance(double yourBalance) { this.yourBalance = yourBalance; }
At this point, the astute learner is probably asking themselves, Wait a minute, how is that functionally any different from when I had public variables? The answer is, it isn’t. If you look through the example above, you’d see that the emphasis was on the visibility of yourBalance, and not how that’s important or how we can manipulate visibility to control access to yourBalance and ericBalance.
A Better Example
Let’s try this again, but this time, with an emphasis on controlling access to sensitive variables.
Let’s take an online bank. Now, this bank knows your bank account balance, stored in the variable yourBalance, and my balance, stored in the variable ericBalance. With public variables, you could get your balance by just referring to bank.yourBalance. However, I could also see your balance by doing the same thing. What’s more, both of us can modify your balance by assigning bank.yourBalance to a new number. Since balances should only be accessible by the person who holds the account, we need to prevent this sort of behavior, so we’re going to make yourBalance and ericBalance private.
So far, practically the same, you’re probably thinking, but bear with me.
Because we want to make sure that the only person who sees the account balance is the actual account holder, our method to get the balance should verify that the person asking for your account balance is you, and that the person asking for my account balance is me. If it isn’t, we should throw an exception.
public double getYourBalance(Credentials credentials) throws InvalidUserSessionException { if (isValidUserSessionFor(you, credentials)) { return yourBalance; } else { throw new InvalidUserSessionException("Cannot return " + "your balance without you being logged in."); } }
See how getters are supposed to work now? Rather than blindly returning yourBalance to anyone who asks, we’re making sure we only return yourBalance to you, and no one else. But what about modifying yourBalance? You don’t want me arbitrarily setting your balance, and your bank doesn’t want you setting your balance to $4,000,000,000,000 simply because you want to feel rich. Here’s the thing about getters and setters no one ever tells you in basic online tutorials: getters and setters refer to types of functions, not just to functions named get<variableName>() and set<variableName>(). You should form your functions accordingly.
Back to the bank thing now.
With a bank, balances get modified in 1 of 2 ways. We either deposit money (increasing the balance) or withdraw money (decreasing the money). For the purpose of this example, we’ll consider withdrawals and debits as basically the same thing and cover them in the same function for simplicity’s sake. So, we need 2 setter methods, 1 for depositing money, and 1 for withdrawing money. Before we can deposit money, we need to make sure wherever this money’s coming from has at least that amount. Before we can withdraw money, we need to make sure the account has at least that amount.
public void depositMoneyIntoYourAccount(sourceAccount, amount) throws InsufficientFundsException { /* * getBalanceFor() is a private method that looks up the balance * for a given account without verifying credentials. However, * we just said it was bad to let people do this, so we made the * method private to limit access to it to just the bank, since * the bank needs to be able reference the balances of its * customers. */ if (getBalanceFor(sourceAccount) >= amount) { yourBalance += amount; } else { throw new InsufficientFundsException("The account " + "you're depositing from doesn't have that " + "much money."); } } public void withdrawMoneyFromYourAccount(credentials, amount) throws InsufficientFundsException, InvalidUserSessionException { /* * Remember only you should be able to withdraw money from your * account, so we need to check that you're the one making the * withdrawal before decreasing your balance. */ if (!isValidUserSessionFor(you, credentials)) { throw new InvalidUserSessionException("Cannot " + "withdraw money from your account without " + "you being logged in."); } else if (yourBalance < amount) { throw new InsufficientFundsException("You don't have " + "the money for that sort of spending."); } yourBalance -= amount; }
As you can see, we’re controlling how your balance changes, and who can withdraw the money. We’re not checking credentials on deposit because you’re probably OK with money going into your account without you explicitly blessing it, but you can easily add that validation too. We’re still getting and setting your balance, we’re just controlling who can do it, and how it’s done.
Hopefully, if you haven’t had a chance to use getters and setters in the wild, this will give you a better idea of how they can be used. Some other things you can do is use the getters and setters to:
- Validate input (in a setter)
- Format output (in a getter)
- Make sure related variables are updated accordingly (in a setter)
- Notify observing processes/clients/etc. of changed state (in a setter)
- Block input until new values are processed (in a setter)
- Update a notification queue (in a getter and/or a setter)
Once you understand that you narrow variable visibility to control access, you can then start to think of your getters and setters as the tools you use to control that access to your sensitive data. Now that you’re armed with this handy perspective, go forth and get and set correctly.