Avoid getters and setters whenever possible

图片[1]-Avoid getters and setters whenever possible - 拾光赋-拾光赋
Noooo!!! Don’t click that generate getters and setters option!!!

I like the rule: “Don’t use accessors and mutators.” Like any good rule, this one is meant to be broken. But when?

First, let me be clear about what I am saying: adding getters and setters to OO classes should be the last resort after considering a series of better alternatives. I believe that a careful analysis yields the conclusion that getters and setters are harmful in the clear majority of cases.

What is the harm?

First let me point out that the “harm” I am talking about might not be any harm at all. The following is, in some cases, a perfectly reasonable class:



// Car1.java

public class Car1 {
  public Engine engine;
}


Enter fullscreen mode Exit fullscreen mode

Notice, though, that feeling of tightening in your stomach, the bristle of your hair, the tensing of the muscles that you may experience looking at something like that.

Now, I want to point out that there’s no meaningful functional difference between that class, a public class with a public class member, and the following class below, a public class with a private member that is exposed by getters and setters. In both classes, Car1.java and Car2.java, we get essentially the same result.



// Car2.java

public class Car2 {
  private Engine engine;

  public Engine getEngine() {
    return engine;
  }

  public void setEngine(Engine engine) {
    this.engine = engine;
  }
}


Enter fullscreen mode Exit fullscreen mode

To show this, I read and write the engine in either Car1.java or Car2.java:



// Car1 member read and write
Car1 car1 = new Car1();
logger.debug("Car1's engine is {}.", car1.engine);
car1.engine = new HemiEngine();

// Car2 member read and write
Car2 car2 = new Car2();
logger.debug("Car2's engine is {}.", car2.getEngine());
car2.setEngine(new HemiEngine();


Enter fullscreen mode Exit fullscreen mode

The point here is that anything I can do with Car2.java, I can do with Car1.java, and vice-versa.
This is important because we’ve been taught to get squeamish when we see Car1.java. We see that public member sitting there and we say, not safe! Engine is not protected by anything! Anyone can do anything! Aaaaaaaaagggghhhhh!

Yet for some reason we breathe a sigh of relief when we see Car2.java. Which–I’m sorry–I personally think is funny since there’s literally the same (non-existent) protections around both of these things.

What could go wrong?

The following are some of the disadvantages of public getters and setters that directly expose a single private member, have the same name, and provide no other functionality.

Getters and setters are a fake insurance policy of isolated change

One supposed advantage of getters and setters is that on the off-chance that the type of a class member needs to change, the change can be limited to inside the class by making the existing getter simply translate from the internal type to the previously-exposed type.



// Car2.java, engine changed to motor

public class Car2 {
  private Motor motor;

  public Engine getEngine() {
    return convertToEngine(motor);
  }

  public void setEngine(Engine engine) {
    this.motor = convertToMotor(engine);
  }
}


Enter fullscreen mode Exit fullscreen mode

My question is how often has the working programmer ever had to do that? I don’t remember ever doing this in all my years of software. Not once have I been able to take advantage of the fake insurance policy that getters and setters provide.

Also, this argument becomes an entirely moot point if the engine had never been exposed to begin with (let’s say it was kept private or package-private). Just expose behavior, rather than state, and you never need to worry about flexibility in changing implementation.

This realization that the private member should not have been exposed triggers another realization that this argument is tautological. Getters and setters expose the private member, and rest the case for their existence on the fact that the private member is exposed.

Getters and setters expose implementation details

Let’s say I give you only the following API to my Car object:



 _________________________________
| Car                             |
|---------------------------------|
| + getGasAmount(): Liters        |
| + setGasAmount(liters: Liters)  |
|_________________________________|


Enter fullscreen mode Exit fullscreen mode

If you assume that this is a gas-powered car that internally tracks gasoline in liters, then you are going to be right 99.999% of the time. That’s really bad and this is why getters and setters expose implementation / violate encapsulation. Now this code is brittle and hard to change. What if we want a hydrogen-fuelled car? We have to throw out this whole Car class now. It would have been better just to have behavior methods like fillUp(Fuel fuel).

Things like this are the reason why famous libraries have terrible legacy classes. Have you ever noticed how most languages have a Dictionary data structure but it’s called Map in Java? Dictionary actually was an interface that was introduced in JDK 1.0, but it had problems and ultimately had to be replaced by Map.

Getters and setters can actually be dangerous

Let me tell you a story about a friend. OK? A friend I said!!!

One day this friend came into work, and found that dozens of well-known web sites in countries around the world all had the header and navigation of the parent corporation’s main web site (not their own), and were using British English. The operations team was frantically restarting hundreds of servers around the globe because for the first half-hour or so that these servers ran, things functioned normally. Then (bam!) all of a sudden something would happen that made the whole thing go sideways.

The culprit was a setter method deep in the guts of a shared platform that all these different sites were using. A little piece of code that ran on a schedule happened to be updated recent to this fiasco that changed the underlying value that determined site headers and languages by calling this setter.

If you only have a getter, things can be just as bad. In Java at least, returning a reference type from a getter provides that reference to the caller and now it can be manipulated by the caller in unexpected ways. Let me demonstrate.



public class Debts {
  private List<Debt> debts;

  public List<Debt> getDebts() {
    return debts;
  }
}


Enter fullscreen mode Exit fullscreen mode

OK, that seems reasonable. I need to be able to see a person’s debts to give them a statement. Huh? What’s that you say? You can add debts now? Shit! How did that happen!



Debts scottsDebts = DebtTracker.lookupDebts(scott);
List<Debt> debts = scottsDebts.getDebts();

// add the debt outside scotts debts, outside the debt tracker even
debts.add(new Debt(new BigDecimal(1000000))); 

// prints a new entry with one million dollars
DebtTracker.lookupDebts(scott).printReport();


Enter fullscreen mode Exit fullscreen mode

Eek!

One way to guard against this is to return a copy instead. Another way is to have an immutable member. The best way, though, is to not expose the member in any way at all and instead bring the behavior that manipulates the member inside the class. This achieves full isolation of the implementation and creates only one place to change.

When getters make sense

Wait a second! If there are so many disadvantages to accessors and mutators, why ever use them?

I’m convinced that getters and setters which just return a class member almost never make sense. But you might write something close to getter/setter functionality as long as you are actually doing something in that method.

Two examples:

  • In a setter, before updating the state in this object according to some input, we validate the input. The input validation is additional functionality.

  • The return type of a getter is an interface. We have therefore decoupled the implementation from the exposed interface.

See, what I’m really advocating for here is a different stance and philosophy towards getters and setters. Rather than say never use accessors and mutators, I want to give you the list of options that I try to exhaust before using one:

  • My “default” is to start with a private final member, set only by the constructor. No getter or setter!

  • If another class absolutely needs to see this member, I think about why. I try to see if there is a behavior that I can expose instead, and create a method for that behavior.

  • If it’s absolutely necessary for some reason, then I relax to package-private (in Java) and expose the member only to other classes in the same package, but no further.

  • OK, what about the data use case? Literally I might need to have an object to pass data across some kind of interface boundary (let’s say to a file system, database, web service, or something). I still don’t futz around with getters and setters. I create the class with all package-private members, and I think of and use it as just a bag of properties. I try to limit these into their own packages and layers at the boundaries of the application.

  • I would consider creating both a getter and a setter for the data use case in a public API, like let’s say I am writing a library meant to be used as a dependency in a lot of other applications. But I would only consider it after exhausting all of these list items.

Wisdom of the Masters

A short postscript. Obviously, there’s debate about getters and setters out there in the world. It’s important to know there’s clearly a camp of “masters” like Robert C. (“Uncle Bob”) Martin who are proponents of avoiding getters and setters. In the book Clean Code, Martin wrote about this in chapter 6:

Beans have private variables manipulated by getters and setters. The quasi-encapsulation of beans seems to make some OO purists feel better but usually provides no other benefit.

Josh Bloch has a nuanced stance in Effective Java, Item 14 that is slightly in favor of getters and setters for public classes, and slightly against for others. He ends up basically saying that what he is really concerned about is mutability, a point I touched on above:

In summary, public classes should never expose mutable fields. It is less harmful, though still questionable, for public classes to expose immutable fields. It is, however, sometimes desirable for package-private or private nested classes to expose fields, whether mutable or immutable.

Further Reading

Here’s some helpful thoughts from smarter people than me.

Tell, Don’t Ask

Why getter and setter methods are evil

Accessors are evil

原文链接:Avoid getters and setters whenever possible

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容