-->

When should encapsulation be used? [closed]

2020-07-22 10:21发布

问题:

I am completing Sun/Oracle's Trail (http://docs.oracle.com/javase/tutorial/java/TOC.html) and it keeps reiterating the importance of encapsulation.

How important, really, is encapsulation? I mean, if I may need to access the value of a given class field, why would I do so through a method when I could just access the field directly? Since the field would be accessed through its corresponding object anyway, where could this really go wrong?

Is it just for code extensibility purposes? In other words, because that way in the future if I decide I want to somehow alter or sanitize the field before returning it I can?

I'm more looking for an example or two than anything.

回答1:

Validation.

If you don't use a method, you can't add any validation on the field unless you validate it at every place you want to access the field: unsustainable.

It also separates the data from your class from the outside world. By hiding the actual implementation of data behind methods, you can manipulate your data the way you want (now and in the future) and no other pieces of code will get broken. This allows you to change the way something is represented without a problem, as long as you make sure it can still be returned trough the existing method.



回答2:

Encapsulation is not only a matter of making getter and setter for a field.

It's about:

  • Validation (and also consistency)
  • Hidding implementation (programming to an interface not an implementation)
  • Getters and setters don't have to reflect the acutal fields. There could be getters (and even setters) for fields which value is calculated on demand
  • Hide complexity: A getter/setter could peform something more complex than just setting a value
  • Advanced: Use of a diffrent implementation/modification; patterns like lazy loading which is used in ORM framework wouldn't work if you would use public fields

Even if you as you said "need to access the value of a given class field" you can't be sure that this requirement won't change (cause it will most time).



回答3:

Actually, I think you're thinking about this the wrong way. The issue isn't encapsulation per se, it's decoupling the behavior of your objects from their data.

Fields are data -- they are part of the internal state of the object. Methods are part of the object's API. Objects shouldn't just be clusters of fields -- if your objects are just dumb collections of data, then that's not object-oriented programming, that's just structured programming.

Objects should be designed to represent real-world entities, and have methods that represent operations you could take on those real-world entities. To put it another way, you don't ask an object for its fields (e.g. getFoo(), getBar()) to pass those to other functions -- instead you should put the relevant operations (e.g. purchase(), validate(), etc.) as methods directly on the object.

That said, there's nothing wrong with having accessor methods -- sometimes you do need to actually retrieve the value. But by making those accessors methods instead of just exposing fields directly, you are implementing information hiding: users of your class don't need to know what the internal state looks like to be able to use it or get data from it.

Basically, in Java (or in any object-oriented language) classes are nouns, and methods are verbs. If you write classes that don't have any verbs, then you're programming in the kingdom of nouns.



回答4:

Encapsulation allows your object to make guarantees (part of an object's contract), by giving the object control over its own data, which happens to make debugging considerably easier. Consider this class:

public class TravelRoute {
    public int distance = 1000;
    public int travelSpeed = 60;

    public int calculateTravelTime() {
        return distance / travelSpeed;
    }
}

Any other code is free to set travelSpeed to zero, which will cause all future calls to the calculateTravelTime method to fail. Worse, you will have no way to know who set it to zero, so debugging the problem is going to take a long time.

However, with encapsulation, the class has total control over the value, and can guarantee that it is always valid:

public class TravelRoute {
    private int distance = 1000;
    private int travelSpeed = 60;

    /**
     * This is GUARANTEED to return a positive value.
     */
    public int getTravelSpeed() {
        return travelSpeed;
    }

    /**
     * Sets this instance's travel speed.
     *
     * @throws IllegalArgumentException if argument is not positive
     */
    public void setTravelSpeed(int newSpeed) {
        if (newSpeed <= 0) {
            throw new IllegalArgumentException("Argument must be positive");
        }
        this.travelSpeed = newSpeed;
    }

    public int calculateTravelTime() {
        return distance / travelSpeed;
    }
}

Now it is absolutely impossible for any outside code to place the object in an invalid state. If anyone tries to do so, the resulting IllegalArgumentException will provide you with an informative stack trace that will immediately expose the culprit.

As a bonus, all other code which uses this class no longer needs to do any checks for its validity, because the object itself can already guarantee that validity. This makes overall development much faster for everyone.