In object-oriented languages, objects consist of a mix of state and behavior. The state is stored in what are variously called attributes, properties, instance variables, or member variables. The behavior is accessed through methods or member functions.
OO languages provide different ways of getting at the state. Some, like Smalltalk and Ruby, make it impossible to directly access the instance variables of an object from the outside. Others, like C++ and Java, provide varying levels of direct access to the instance variables.
In all of these languages, it is possible to define methods that allow access to the state. These methods are known as getters for reading the state, and setters for modifying the state. Collectively, they are known as accessors.
Some languages make it easy to define accessors. Ruby provides the
attr_accessor methods that
automatically generate basic getters and setters. Smalltalk itself
provides no such convenience, but often the development environment
provides a way of generating accessors. Swift hides all state behind
automatic getters and setters that you can choose to customize if you
Regardless of how easy or hard it is to get access to the state of an object, it’s almost never a good idea to do so. I try very hard not to provide public getters and setters on my objects, because they are almost always an indication of a design smell - something not quite right with my design.
Public setters allow outside code to directly change an instance variable in my object. This is almost never desirable.
There are two cases to consider:
The setter might be providing an initial value for the instance variable. In this case, I’d much rather provide the value via the constructor or initialization function. That way, the object is born fully-formed and ready to be used for any purpose. There are also no ordering dependencies to be aware of (“make sure you set
foobefore you call any methods”).
The setter might be changing an existing value in the object. While this might be necessary in some cases, there’s usually a reason for this new value. I’d rather have a method named for that reason rather than just exposing the state. That way, future changes to the internals of the object won’t leak out to all of the clients calling the setter.
If you’re influenced at all by ideas from functional programming, such as Gary Bernhardt’s Functional Core, Imperative Shell, you might want to make your objects immutable in many cases. Public setters violate immutability.
Public getters allow outside code to directly read the value of an instance variable. This might not be too bad if the value is a primitive or an immutable object, but returning a reference to a mutable object (like a collection or similar) is asking for trouble.
Public getters make it easy for a rushed programmer to introduce Feature Envy and violations of the Tell, Don’t Ask principle. Rather than thinking about the design and adding appropriate behavior to objects, we just grab the values and do the calculation we need with them.
Private setters are almost never needed. If you need to change the value of an instance variable, just change it. That said, sometimes there needs to be additional action taken when the value is changed. In that case, encapsulating that action in a setter method is a good idea.
Private getters are almost never needed. You can just use the instance variable directly.
However, I now almost always write private getters in Ruby because it
gets rid of the leading
@ on the variable. It also makes it easier
to refactor, as beautifully demonstrated by Avdi Grimm in the
Barewords episode of Ruby Tapas.
When I used to follow a state-based testing style, I’d often add getters and setters for the tests to use. That way, I could get at the state of the object in order to set up a test or to assert something. I’d always mark these accessors as “testing-only”.
Since transitioning to a more mock-based testing style, I find I don’t have to do this any longer.
The next time you’re tempted to add a public accessor to an object you’re writing, stop and think. Is there another way to solve your problem that doesn’t require the accessor? What happens to your code when you write it this way?