Self Encapsulate Field

You are accessing a field directly, but the coupling to the field is becoming awkward.

Create getting and setting methods for the field and use only those to access the field.

  private int _low, _high;
  boolean includes (int arg) {
      return arg >= _low && arg <= _high;
  }
graphics/arrow.gif
  private int _low, _high;
  boolean includes (int arg) {
      return arg >= getLow() && arg <= getHigh();
  }
  int getLow() {return _low;}
  int getHigh() {return _high;}

Motivation

When it comes to accessing fields, there are two schools of thought. One is that within the class where the variable is defined, you should access the variable freely (direct variable access). The other school is that even within the class, you should always use accessors (indirect variable access). Debates between the two can be heated.

Essentially the advantages of indirect variable access are that it allows a subclass to override how to get that information with a method and that it supports more flexibility in managing the data, such as lazy initialization, which initializes the value only when you need to use it.

The advantage of direct variable access is that the code is easier to read. You don't need to stop and say, "This is just a getting method."

I'm always of two minds with this choice. I'm usually happy to do what the rest of the team wants to do. Left to myself, though, I like to use direct variable access as a first resort, until it gets in the way. Once things start becoming awkward, I switch to indirect variable access. Refactoring gives you the freedom to change your mind.

The most important time to use Self Encapsulate Field is when you are accessing a field in a superclass but you want to override this variable access with a computed value in the subclass. Self-encapsulating the field is the first step. After that you can override the getting and setting methods as you need to.

Mechanics

  • Create a getting and setting method for the field.
  • Find all references to the field and replace them with a getting or setting method.
    Replace accesses to the field with a call to the getting method; replace assignments with a call to the setting method.

    You can get the compiler to help you check by temporarily renaming the field.

  • Make the field private.
  • Double check that you have caught all references.
  • Compile and test.

Example

This seems almost too simple for an example, but, hey, at least it is quick to write:

  class IntRange {
 
    private int _low, _high;
 
    boolean includes (int arg) {
        return arg >= _low && arg <= _high;
    }
 
    void grow(int factor) {
        _high = _high * factor;
    }
    IntRange (int low, int high) {
        _low = low;
        _high = high;
    }

To self-encapsulate I define getting and setting methods (if they don't already exist) and use those:

  class IntRange {
 
    boolean includes (int arg) {
        return arg >= getLow() && arg <= getHigh();
    }
 
    void grow(int factor) {
        setHigh (getHigh() * factor);
    }
 
    private int _low, _high;
 
    int getLow() {
        return _low;
    }
 
    int getHigh() {
        return _high;
    }
 
    void setLow(int arg) {
        _low = arg;
    }
 
    void setHigh(int arg) {
        _high = arg;
    }

When you are using self-encapsulation you have to be careful about using the setting method in the constructor. Often it is assumed that you use the setting method for changes after the object is created, so you may have different behavior in the setter than you have when initializing. In cases like this I prefer using either direct access from the constructor or a separate initialization method:

    IntRange (int low, int high) {
        initialize (low, high);
    }
 
    private void initialize (int low, int high) {
        _low = low;
        _high = high;
    }

The value in doing all this comes when you have a subclass, as follows:

  class CappedRange extends IntRange {
    CappedRange (int low, int high, int cap) {
        super (low, high);
        _cap = cap;
    }
 
    private int _cap;
 
    int getCap() {
        return _cap;
    }
 
    int getHigh() {
        return Math.min(super.getHigh(), getCap());
    }
   }

I can override all of the behavior of IntRange to take into account the cap without changing any of that behavior.