Friday, June 18, 2010

Symmetric getters and setters

Using getters and setters is a very common pattern for classes in Java projects. In general, I think they should be avoided, especially since setters eliminates the possibility of having final fields. Also, blindly adding getters to your classes is a bad idea since you expose implementation details to the outside world. Of course, some times you do no need them, and the rest of this post assumes that you do.

Sometimes you want to do some preprocessing on the inputs to the setters, to support cases where the input is out of range or other things of that nature. For instance, your domain objects may have a requirement that the values of the field should be in a specific range. You can then either:
  • Throw an illegal argument exception - if the ranges are clearly defined and the caller should know them, this is a valid approach.
  • Silently clamp the input value to be inside the range. This can sometimes be the desired approach, but it ultimately depends on the system.
  • Accept the value as it is, and just hope that the callers behave nicely. This is usually not a good idea, and should only be done if you control all the callers and its in a cpu critical section of the code.
There is a symmetry law which I think should hold for pairs of getters and setters.
Note that is a completely personal opinion. I don't expect everyone to agree with it. It's a good thing this blog is also completely personal!

Anyway, here is the law:
Values provided from a getter should be legal values when calling the setter, and if you run the following code, then x and y should be equal:

x = obj.getFoo();
obj.setFoo(x);
y = obj.getFoo();
assert x.equals(y);


For simple getters and setters that just uses a single field, this obviously always holds, even if the setters throws exceptions on bad values, or clamps the values out of range. This probably one of the reasons that many people assume that this symmetry usually holds.

However, sometimes the getters and setters don't really correspond to a specific field, but instead triggers other code to be modified. In this case, it's not clear that the symmetry holds.

for instance, you may have something like this:

public class Foo {
private final Bar bar;
public void setValue(int value) {
bar.setValue(value);
}

public int getValue() {
return offset + bar.getValue();
}
}

This is clearly asymmetric, so how do you fix it?
You could try something like this, but it's very prone to error, depending on how bar.setValue is implemented:

public void setBar(int value) {
bar.setValue(value - offset);
}


A better option is to remove the setter entirely and expose the bar object directly. I don't think anyone would take for granted that foo.getBar().setValue(...) needs to be symmetric with foo.getValue()

Yet another variant is simply renaming setValue to setBarValue (or setBaseValue or setDelegateValue, or something else, depending on the context) - this simple transformation also makes it clear that it's not symmetric.

No comments:

Post a Comment