Calculating length with different units We have an exercise to design a simple library, which can calculate some length with different unit, e.g.

2m*2 + 20cm/2 - 5mm


and the result can be any of:

4.095m
409.5cm
4095mm


I want to design it in Object-oriented, and have a solution like this:

public class Length {

    private final double value;
    private final Unit unit;

    public Length(double value, Unit unit) {
        this.value = value;
        this.unit = unit;
    }

    @Override
    public boolean equals(Object obj) {
        Length length = (Length) obj;
        return this.unit.toMM(this.value) == length.unit.toMM(length.value);
    }

    public Length add(Length added) {
        return new Length(this.unit.toMM(this.value) + added.unit.toMM(added.value), Unit.mm);
    }

    public Length subtract(Length another) {
        return new Length(this.unit.toMM(this.value) - another.unit.toMM(another.value), Unit.mm);
    }

}

enum Unit {
    m(1000), cm(10), mm(1);
    private final int rate;

    Unit(int rate) {
        this.rate = rate;
    }

    public double toMM(double value) {
        return rate * value;
    }
}


It works, but I have several questions:


Is there anything wrong from the OO point of view?
Unit has a method toMM which is bind to a concrete type of unit mm(millimeter), I don't feel good about it, is there any way to improve it? Or it's just acceptable since we have to choose one?
Length has chosen mm for all kinds of unit, is it good? I'm looking for a way to avoid it, but not found


These questions are mostly based on this possible requirement change: don't want to support mm anymore. Most of the code has to be modified in this case.



Update: from this article, seems it's not a good idea to use enum here for different units?
An alternative approach would be to say that a length is measured in SI units (meters).

Also, if performance is not a concern, using a BigDecimal instead of a double would avoid potential rounding errors.

It could look like:

public class Length {
  private final BigDecimal meters;
  public Length(BigDecimal meters) { ... }
  public Length(BigDecimal value, Unit unit) { /*convert to m*/}

  public BigDecimal getLength(Unit unit) { /*convert to unit*/ }
  public BigDecimal getLength() { /*return length in SI, i.e. meters*/ }

  public int hashCode() { return meters.hashCode(); }
  public boolean equals(Object o) {
    /*usual checks */
    return meters.compareTo(other.meters) == 0;
  }
  //other methods
}


This also avoids having to arbitrarily choose a "base" unit (mm in your example).

Finally I would suggest defining an interface for Unit:

public interface Unit {
  BigDecimal toMeters(BigDecimal length);
  //and possibly additional helper methods:
  default BigDecimal convert(BigDecimal length, Unit sourceUnit) { ... }
}


Then your enum would be:

public enum StandardUnit implements Unit {
  MM("0.001"); //etc.

  private final BigDecimal multiplier;
  StandardUnit(String mulltiplier) { this.multiplier = new BigDecimal(multiplier); }
  public BigDecimal toMeters(BigDecimal length) { return length.multiply(multiplier); }
}


This will allow a more flexible design where you can add units by implementing the interface or use the provided standard units.