6

I’m currently building a unit converter app in Java (as a beginner), and I want to avoid duplicating logic for each type of unit.

Right now, I have different enums like Length, Weight, Time, etc., and each enum constant has a conversion factor to a base unit (e.g. meters, kilograms, etc.). These enums all implement a common Units interface:

public interface Units {}

public enum Length implements Units {
    METRE(1.0), KILOMETRE(1000), CENTIMETRE(0.01), ...;

    private final double toMetreFactor;

    Length(double factor) {
        this.toMetreFactor = factor;
    }

    public double getToMetreFactor() {
        return toMetreFactor;
    }
}

In my ConverterModel class, I currently have methods like this:

public double convertLength(double value, Length input, Length output) {
    double base = value * input.getToMetreFactor();
    return base / output.getToMetreFactor();
}

But this means I need to write similar methods for every category (convertWeight, convertTime, etc.).

I’d like to generalize this logic so I can write one reusable method like:

public double convert(double value, Units from, Units to);

I already have my GUI set up with JComboBox, so I’m passing the selected units as Units. However, I can’t access the conversion factor unless I cast to the specific enum (e.g., Length), which feels clunky and not extensible.

Is it possible to design the Units interface (or the enums) so that I can call something like from.getToBaseFactor() without casting? Or can I somehow make the convert(...) method universal?

1
  • As a rough guess just putting the unit into an enum and keeping the numbers separate is unfortunate for conversion. How about a class that encapsulates the number plus the unit and thus can be converted easily? Commented May 10 at 20:38

2 Answers 2

9

Move the common method (getToBaseFactor) to the interface.

public interface Units {
    double getToBaseFactor();
}

and rename the getter in Length to getToBaseFactor:

public enum Length implements Units {
    // ...

    public double getToBaseFactor() {
        return toMetreFactor;
    }
}

Then you can write a general convert method like this:

public double convert(double value, Units from, Units to) {
    double base = value * from.getToBaseFactor();
    return base / to.getToBaseFactor();
}

Note that this design allows you to do nonsensical conversions, e.g. from meters to kilograms. You can prevent yourself from accidentally doing that by making Units generic.

public interface Units<TSelf extends Units<TSelf>> { ... }
public enum Length implements Units<Length> { ... }

public <T extends Units<T>> double convert(double value, T from, T to) { ... }
Sign up to request clarification or add additional context in comments.

2 Comments

I think your generic approach is nice, and could be built into Units as default double convert(double value, TSelf to)
@DuncG unit1.convert(100, unit2) doesn't feel as readable to me. If I were going for a more readable approach, I would write a Measurement<TUnit> that wraps a double and a TUnit, then put a convertTo(TUnit) method in that, returning a Measurement<TUnit> or mutating this.
0

A very minor change to the answer by Sweeper would put convert methods inside Unit:

public interface Units<TSelf extends Units<TSelf>> {
    double getToBaseFactor();

    default double convert(double value, TSelf to) {
        double base = value * getToBaseFactor();
        return base / to.getToBaseFactor();
    }
    static <T extends Units<T>> double convert(double value, T from, T to) {
        return from.convert(value, to);
    }
}

Then you could override convert per enum if needed, and call in two ways:

System.out.println(Length.METRE.convert(2.0, Length.CENTIMETRE));
System.out.println(Length.METRE.convert(1500.0, Length.KILOMETRE));
System.out.println(Units.convert(1500.0, Length.METRE, Length.KILOMETRE));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.