In my experience there are almost no situations where a setter method is a good idea. The wholesale replacement of a piece of an object’s internal state at arbitrary times leads to a class of interaction defects that are horribly difficult to track down. Most of the time solving them involves hooking up the debugger and stepping through going, ‘its fine here. Its fine here. Its still fine here. Its not fine here. Who called setName with an invalid value?’ and then debugging the call history of the method that made the bad call. Or alternatively, knowing that you have to call something like ‘accountService.updateBalance(account)’ before you can say ‘account.getBalance()’ or you’ll get a null back.
A class with all fields marked as final and initialized through its constructor is robust and easier to use than one with setter methods. You know that any time you have a reference to an instance of it, it’s ready to use and you can call methods without worrying about random fields not being set.
Note that just because all the fields are final and there are no setters, this doesn’t mean that the object is immutable. It just means that its behaviour more closely models reality, and your interactions with it will be far more meaningful. account.credit(amount) and account.debit(amount) instead of account.setBalance(amount). The balance is the sum of all the credits and debits, not something anyone can arbitrarily change (although I’d love it if I could just call my bank and say ‘please set my account balance to 1 million pounds’).
Which brings me on to my next point. I often see setters used where information relating to an object is not known at construction time, and needs to be added later. This is usually a symptom of choosing the wrong class to store that data. To continue the bank account example, a bank account (at least here in the UK) has certain properties. It has a sortcode and an account number, and probably an account name. These properties are pretty much fixed for the life of the account. (Sometimes they do change, but that is often dealt with as an account closure and opening of a new account with the funds transferred). There are certain things that an account does not have, which may surprise you.
A bank account does not have a balance. Not in the same way it has an account number. What an account does have is a series of credits and debits over time. When I call up my bank and ask for my account balance, what I’m really asking for is ‘the balance as of right now’. The snapshot of money in and money out since the account was opened that adds up to what is in it today.
Now lets say we want to encapsulate the concept of a balance instead of having to calculate the value from the list of credits and debits. I need the account it relates to, the point in time I’m interested in and, of course, the balance of the account at that time. A Balance class might therefore be constructed as ‘new Balance(Account account, Date asOf, Money amount)’. Those 3 pieces of information combined are immutable. They will never change. No matter how often I check my balance from the 10th of April it will always have the same value.
Thus its fine for a Balance to have an Account (and in fact completely meaningless for a Balance not to have an Account), but an Account does not have a Balance. Getting this kind of relationship right is at the heart of OO design.