views:

137

answers:

3

Here is a small currency converter piece of code:

 public enum CurrencyType {

        DOLLAR(1),
        POUND(1.2),
        RUPEE(.25);

        private CurrencyType(double factor) {
            this.factor = factor;
        }
        private double factor; 

        public double getFactor() {
            return factor;
        }

    }

    public class Currency {

        public Currency(double value, CurrencyType type) {
            this.value = value;
            this.type = type;
        }

        private CurrencyType type;
        private double value;

        public CurrencyType getCurrencyType() {
            return type;
        }

        public double getCurrencyValue() {
            return value;
        }

        public void setCurrenctyValue(double value){
            this.value = value;
        }

    }

public class CurrencyConversion {


    public static Currency convert(Currency c1, Currency c2)
            throws Exception {
        if (c1 != null && c2 != null) {
            c2.setCurrenctyValue(c1.getCurrencyValue()
                    * c1.getCurrencyType().getFactor()
                    * c2.getCurrencyType().getFactor());
            return c2;
        } else
            throw new Exception();
    }


}

I would like to improve this code to make it work for different units of conversion, for example: kgs to pounds, miles to kms, etc etc. Something that looks like this:

public class ConversionManager<T extends Convertible> {


    public T convert(T c1, T c2) 
    {
        //return null;
    }
}

Appreciate your ideas and suggestions.

+1  A: 

Enums in java unfortunately don't support interfaces or superclasses (I'd like to figure out the proper reason for this).

What you can do is to declare an annotation on your enum types (eg, @Convertible), and access its factor using reflection. This is quite ugly, and you won't be able to use "T extends Convertible" expressions in generics.

Gabor Farkas
@Gabor yep, the fact that one cannot extend Enums while using generics sucks. Using custom annotations is something that I want to use as a last resort.
Jay
+2  A: 

This is the object-oriented way to do it in my opinion. Note that you could templatize on the data type as well but I've just used doubles here since they can represent any scalar units. This provides a nice way of handling conversion between units of different metrics as you asked: distance, currency, mass, etc.

public interface Metric {
    public String getReferenceUnit();
}

public class Distance implements Metric {
    public String getReferenceUnit() {
        return new String("Meters");
    }
}

public interface Units<T extends Metric> {
    public double getToReferenceFactor();
}

public class Kilometers implements Units<Distance> {
    public double getToReferenceFactor() {
        return 1000;
    }
}

static <T extends Metric> double convert(Units<T> from, Units<T> to) {
    return from.getToReferenceFactor() * (1.0 / to.getToReferenceFactor());
}
bshields
thank you for your answer. Here is a question: What is the point of getReferenceUnit() method?
Jay
The idea is that there is a reference unit for each metric and every other unit defines itself in relation to the reference unit. In your original example, the reference unit was dollars and the other currencies were represented as a conversion factor from dollars. I added the getReferenceUnit() function here just to illustrate that for distance the reference unit would be meters and therefore the Kilometers unit implementation would have 1000 as it's conversion factor.
bshields
+1  A: 

Here is a universal converter that uses a hollow matrix, and that does a transitive closure to combine known converters. In this case, it converts length units:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Converter {
    private static final Map<String, Map<String, Double>> FACTOR_MAP
            = new HashMap<String, Map<String, Double>>();

    public static double convert(String from, String to, double value) {
        List<Step> stack = new ArrayList<Step>();
        stack.add(new Step(from, value));
        while (!stack.isEmpty()) {
            Step s = stack.remove(0);
            double val = s.value;
            String source = s.unit;
            if (source.equals(to)) {
                return val;
            }
            Map<String, Double> map = FACTOR_MAP.get(source);
            if (map != null) {
                for (Map.Entry<String,Double> entry: map.entrySet()) {
                    stack.add(new Step(entry.getKey(), val*entry.getValue()));
                }
            }
        }
        throw new IllegalArgumentException("Cannot not convert from " + from
                + " to " + to);
    }

    public static void registerFactor(String from, String to, double factor) {
        putFactor(from, to, factor);
        putFactor(to, from, 1.0/factor);
    }

    private static void putFactor(String from, String to, double factor) {
        Map<String, Double> map = FACTOR_MAP.get(from);
        if (map == null) {
            map = new HashMap<String, Double>();
            FACTOR_MAP.put(from, map);
        }
        map.put(to, factor);
    }

    static {
        registerFactor("cm", "mm", 10);
        registerFactor("in", "cm", 2.54);
        registerFactor("in", "pt", 72);
        registerFactor("pc", "pt", 12);
        registerFactor("px", "mm", 0.28);
    }

    private static class Step {
        private String unit;
        private double value;

        Step(String unit, double value) {
            this.unit = unit;
            this.value = value;
        }
    }
}

The following program:

public class Main {
    private static final String UNITS[] = {"cm", "mm", "in", "pt", "pc", "px"};

    public static void main(String[] args) {
        for (String unit1: UNITS) {
            for (String unit2: UNITS) {
                System.out.println("1" + unit1 + " = "
                        + Converter.convert(unit1, unit2, 1) + unit2);
            }
        }
    }
}

gives the following results:

1cm = 1.0cm
1cm = 10.0mm
1cm = 0.39370078740157477in
1cm = 28.346456692913385pt
1cm = 2.3622047244094486pc
1cm = 35.71428571428571px
1mm = 0.1cm
1mm = 1.0mm
1mm = 0.03937007874015748in
1mm = 2.8346456692913384pt
1mm = 0.23622047244094485pc
1mm = 3.571428571428571px
1in = 2.54cm
1in = 25.4mm
1in = 1.0in
1in = 72.0pt
1in = 6.0pc
1in = 90.71428571428571px
1pt = 0.035277777777777776cm
1pt = 0.35277777777777775mm
1pt = 0.013888888888888888in
1pt = 1.0pt
1pt = 0.08333333333333333pc
1pt = 1.2599206349206347px
1pc = 0.42333333333333334cm
1pc = 4.233333333333333mm
1pc = 0.16666666666666666in
1pc = 12.0pt
1pc = 1.0pc
1pc = 15.119047619047619px
1px = 0.028000000000000004cm
1px = 0.28mm
1px = 0.011023622047244094in
1px = 0.7937007874015748pt
1px = 0.06614173228346457pc
1px = 1.0px
Maurice Perry
I like this one. But, don't you think there is a dependency on all these string definitions now? Yes, ofcourse they can be externalized, but the idea is to leave the design open, so that you don't have to modify existing code to add new behavior. For instance, if I were to add a new "factor definition", I would like to do this by adding a new class and sending it in as a param to my program for conversion.
Jay
This is just a example. Nothing prevents you from using anything else than strings (event plain Objects).
Maurice Perry