Daniel Pitts’ Tech Blog

Please vote no on 8.
Please vote no on 8.

Avoiding Primitive Obsession

Daniel, October 28th, 2007

Particularly (but not just) programmers who have a background in C tend to think of simple data as primitive data. Measurements are one of the simplest types of data, so why not do something like this:

public class Bob {
  double distance;
  double duration;
  double weight;
  /* ... rest of code which handles calculation based on distance,
          duration, and volume, based on some predefined units for each. */
}

While there isn’t anything broken about this code, it needlessly couples the algorithms used in Bob with the units used to express distance, duration, and weight. A more robust design would give each of these there own types, which could be unit-independent. We’ll deal with just Distance. I’ve decided to implement the Distance class use meters as its underlying representation, but this fact is hidden from all clients of the class. Furthermore, I’ve tried hide that fact from as many internal methods as possible.

public final class Distance {
    private final double meters;

    private  Distance(double meters) {
        this.meters = meters;
    }

    public double getMeters() {
        return meters;
    }

    public static Distance fromMeters(double meters) {
        return new Distance(meters);
    }

    public Distance times(double scalar) {
        return fromMeters(getMeters()*scalar);
    }

    public Area times(Distance distance) {
        return Area.fromSquareMeters(getMeters() * distance.getMeters());
    }

    public Distance plus(Distance distance) {
        return fromMeters(getMeters() + distance.getMeters());
    }
}

So, what do we have here? Distance now has methods that give you more meaningful operations. A Distance multiplied by another Distance is specifically an Area, but a Distances multiplied by a scalar (simple number), is just a scaled Distance. You can’t add an arbitrary scalar to Distance on accident.

If we were using simple double distance and used the convention that distances was measured in meters, we might see code like distance+=3; // add three meters Unfortunately, if we switch to a different metric, we just broke that line. Using the non-primitive Distance, the same line would be distance = Distance.plus(Distance.fromMeters(3)); Now, no one is constrained to use meters. We could easily add fromInches, getInches, fromYards, getMicrons, etc… We could also decide change the internal representation of Distance to use BobUnits (3.14 Bob Units is one Meter ), and no client code need be touched!

Another useful example are measurements which usually have other associated values with them. For example, Angles. Angles can be measured in Degrees, Radians, and many other units. They also have associated sine, cosine, tangent. So, lets take a look at an Angle class:

public final class Angle {
    private final double radians;

    private Angle(double radians) {
        this.radians = radians;
    }

    public double cosine() {
        return Math.cos(getRadians());
    }

    public double sine() {
        return Math.sin(getRadians());
    }

    public static Angle fromCartesian(Distance x, Distance y) {
        return fromRadians(Math.atan2(y.getMeters(), x.getMeters()));
    }

    public static Angle fromRadians(double radians) {
        return new Angle(radians);
    }

    public Angle plus(Angle angle) {
        return fromRadians(getRadians() + angle.getRadians());
    }

    public double getRadians() {
        return radians;
    }
}

Again, we arbitrarily choose radians as the implementations unit, and again we don’t expose this to the client, and we don’t rely on it internally unless we have to. We have an Angle plus an Angle is an Angle. We also have a way to get an angle from a Cartesian coordinate.
What’s more, we have sine() and cosine(). That’s the big deal with Angle! Now our clients don’t need to use ugliness such as “Math.cos(angle * degreeToRadians)”. If they have an Angle, they can get the sin, cos, degrees, radians, etc… Without having to know the details of the underlying units.

Now, any of these can be turned into interfaces or abstract classes, and then you could have different implementations based on the system needs. For instance, if you’re dealing with subatomic measurements, meters doesn’t make sense for distances, and seconds doesn’t make sense for durations. You could have a MicronDistanceImpl implementation of Distance, and NanosecondDurationImpl implementation of Duration. Similarly for a cosmic simulation, you could have light-years for Distance and millennia for Duration.

Oh, almost forgot. This also gives you the ability to have nice toString() representations. I haven’t added those to the above classes, but I could imagine the toString printing “12.0 meters” and “32.6 degrees”. This will help when presenting these values to a user as well.

This kind of abstraction is what makes good design. Next time you declare something as a primitive (or a dumb primitive wrapper such as Double, or Integer), think about what type units you might assign to it. You’ll find that your classes become simpler as well, since they don’t get bogged down in the formulas needed to convert between units. It also gives you much more expressive power. Think of a Speed class which has Distance and Duration fields. Maybe even a method on Distance public Speed divide(Duration duration);

Tags: , , , , , ,

4 Responses to “Avoiding Primitive Obsession”

  1. Chris Says:

    If you are going to make your technical article(s) available to the general public, you should ensure that (at minimum), you run a spell check across them :-)

    simplist
    millenia
    measuments
    trackback

    Best wishes,
    Chris

    PS: You seem to be a very helpful contributor to the group, unlike some others.
    Keep up the good work :-)

  2. Daniel Says:

    Thanks for pointing those out. For an article that large, I feel luck thats all I mistyped. My spell checker actually rejected millennia, which I *think* is the right word.
    Measuments and simplist are just typos

    trackback is a technical term related to blogs. Its standard for wordpress, as well as other blogs :-)

    Anyway, I’ve corrected the mistakes you pointed out.
    Thanks,
    Daniel.

  3. Lucas Says:

    Getting away from primitives and embracing immutable data types (where appropriate) is definitely something that one learns to appreciate after working on any code base of moderate complexity.

    In the save vein of discussion, have you looked at the new Java measures package, JSR-275.

    http://jcp.org/en/jsr/detail?id=275

    I think this will be a big win for maintainable programs that deal with units and unit conversion.

  4. Daniel Pitts’ Tech Blog » Blog Archive » Almost Useful: Operator overloading Says:

    [...] API, or other custom libraries that are similar. It becomes especially useful when trying to avoid primitive obsession, and create numeric-like [...]

Leave a Reply