At work they're required, but not allowed to contain any logic. They feel pretty useless.
I think the main use cases are:
- Allow internals to change without affecting the API (what is an attribute now may not always remain like that).
- Allow different visibility for read and write, i.e. public getter and protected or non-existent setter.
- (Technically you could override getters/setters but not attributes, but that seems like a horrible idea in almost any case)
I don't think they should always be used, but sometimes the benefits apply. Some of my opinions:
- Some classes are just intended to transfer data (value objects, data classes). Public attributes make sense in that case.
- In general when an attribute
Xis an implementation detail that you want to encapsulate,
setXare not genius names. Describe the behavior, not the storage layout.
- Various languages have various kinds of sugar for this. Some dynamic ones allow replacing an attribute by a property (without needing
()), like Python
@property. Some allow one to attach functions to properties, or even to names that don't have a storage representation. Some call setters when you do
x.a = 7(Kotlin). I generally like this, though it does hide potentially expensive operations.
- The vast majority of attributes should be immutable, or at least not publicly mutable. So be conservative with setters. Private attribute + getter is a good way for internally-mutable data.
- If attributes should be publicly mutable, then getters should protect the internal state. Validations are absolutely appropriate in setters.
- If multiple attributes are interdependent with only some combinations allowed, consider making setting them an atomic operation by only having a method that (validates and) updates all of them.
- If an attribute is mutable (e.g. list), then a getter should return an immutable view of it. Don't do
library.get_books().add_all(my_books). The library should be responsible for validating it's collection of books.
- If you have a policy of having no logic in getters/setters, probably just don't have getters and setters.
- It might be wise to not put heavy computations inside getters (or setters), as this surprises people (and they shouldn't have to read the code, encapsulation, remember?).
- Some people adhere to command/query separation, which I think would suggest not mixing getting and setting (i.e. in Rust-terms, any
&mut selfmethod should return nothing). I am not decided about this.
- I'm hoping most static languages aggressively inline setters and getters so that performance is not important for getters/setters without logic. I might be wrong about that.
So to summarize: prefer a language that has some sugar for getters/setters, try to describe behaviour instead of data, use 'getters'/'setters' to protect validity of internal state, and prefer immutability.