Solved

Java designing a System of Units (Continued)

Posted on 2014-09-15
15
144 Views
Last Modified: 2014-09-28
Continuing my thread
http://www.experts-exchange.com/Programming/Languages/Java/Q_28518241.html

I came across this problem (see Measurement2.gif)

I want every Measurement to have a .getValueAs() method. But for LengthMeasurement I want the parameter restricted to be LengthUnits, and for TemperatureMeasurement I want the parameter to be restricted to TemperatureUnits.

So I put .getValueAs(LengthUnits u) in the LengthMeasurement Class, and .getValueAs(TemperatureUnits u) in the TemperatureMeasurement Class.

Then I add .getValueAs(Units u) in the Measurement Class (the superclass). And that messes everything up!

Is there a way to subclass a method and restrict its parameters?

I don't think so. If Measurement has a .getValueAs(Units u) method which accepts any Unit, and a LengthMeasurement isa Measurement, then it too must accept any Unit. (I think that's transitivity, or some math word like that.)

(I also include uml-f.gif, my latest design which contains the above flaw.)


Testing again the idea of Generics, it looks like I can achieve the restrictive functionality I'm seeking using Generics. (See Measurement2b.gif)

But this gets me back to my earlier design which I started with in my previous post (see link above).


Going down this road again, I have:
Measurement<Q extends Units<Q extends Units>>

(Well I gave it a try and just entered another snag, so I'll just post this question and go to bed. A bit "Thank You!" to anyone who replies!)
Measurement2.gif
uml-f.gif
Measurement2b.gif
0
Comment
Question by:deleyd
  • 5
  • 4
  • 4
  • +1
15 Comments
 
LVL 26

Accepted Solution

by:
dpearson earned 334 total points
Comment Utility
If you really prefer this design, I think you can still make it work.

Here, to me, LengthMeasurement is what before we were calling Units classes (in your previous question).  So I think we'd have:

public abstract class LengthMeasurement extends Measurement {
    private final double m_Value ;
    public LengthMeasurement(double value) { m_Value = value ; }
    public double getValue() { return m_Value ; }
}

public class InchesMeasurement extends LengthMeasurement {
     public InchesMeasurement(double value) { super(value) ; }
}

public class FeetMeasurement extends LengthMeasurement {
     public FeetMeasurement(double value) { super(value) ; }
}

Then the Units would be an enum (I vaguely saw you discussing this on another question, so apologies if this is well trodden ground) but I'd expect something like this:

pulbic interface Units { }

public enum LengthUnits implements Units
{
     kInches, kFeet, kMeters, kMiles
}

public enum TemperatureUnits implements Units
{
    kCelsius, kFarenheit, kKelvin
}

Now your conversion methods that look like:

public class InchesMeasurement extends LengthMeasurement {
     public LengthMeasurement getValueAs(LengthUnits units) {
          switch (units) {
                case kFeet: return new FeetMeasurement(getValue() * 12.0) ;
                 ...
          }
     }
}

public class FeetMeasurement extends LengthMeasurement {
     public LengthMeasurement getValueAs(LengthUnits units) {
          switch (units) {
                case kInches: return new InchesMeasurement(getValue() / 12.0) ;
                 ...
          }
     }
}

or you could save a bit of code and move this up to the LengthMeasurement class:
public abstract class LengthMeasurement extends Measurement {
     // Other methods from above not shown.  Just new methods.

     // Every subclass implements a standard conversion to inches
     protected abstract double getValueInInches() ;

    // Then we only need one method to convert to any other length unit
     public LengthMeasurement getValueAs(LengthUnits units) {
          switch (units) {
                case kFeet: return new FeetMeasurement(getValueInInches() * 12.0) ;
                 case kMeters: return new MetersMeasurement(getValueInInches() * 36.0 * 0.9441) ;
                 ...
          }
     }
}

This gives you a class hierarchy that I think captures everything you need.

However, what this design does not have is a

public class Measurement {
    public Measurement getValueAs(Units u) ;
}

because I don't see what possible semantics that would have.  If you offer that method, in English it means "given any measurement convert it to any units".  But as we know - lots of those are invalid (convert 95 degrees F to inches).

So I don't think that method should exist in a base class.  If you have a LengthMeasurement, you can convert it to any other LengthUnits.  And vice versa for Temperatures.  But you can't do conversion operations on a base class.

You could however have:
public abstract class Measurement {
    public abstract double getValue() ;
    public abstract String getAsString() ;
}

because those methods can be applied to any measurement.

Any clearer?

Doug
0
 
LVL 26

Assisted Solution

by:dpearson
dpearson earned 334 total points
Comment Utility
You could also store the Units with the measurement if you wish - but it's actually not really needed and may just confuse you?

public abstract class LengthMeasurement extends Measurement {
    private final double m_Value ;
    private final LengthUnits m_Units ;
    public LengthMeasurement(double value, LengthUnits units) { m_Value = value ; m_Units = units ; }
    public double getValue() { return m_Value ; }
}

public class InchesMeasurement extends LengthMeasurement {
     public InchesMeasurement(double value) { super(value, LengthUnits.kinches) ; }
}

public class FeetMeasurement extends LengthMeasurement {
     public FeetMeasurement(double value) { super(value, LengthUnits.kFeet) ; }
}

Note that this value is stored, but not used anywhere in the code as the conversion logic is implied by the class's implementation.  An "InchesMeasurement" class is storing inches, because of how it behaves.
The LengthUnits.kinches is largely for documentation or for use in output statements in this code rather than functionality.

Doug
0
 
LVL 35

Expert Comment

by:mccarl
Comment Utility
Ok, I won't make any judgements on what is the best design, because as mentioned before, it is really a personal choice sort of thing. BUT, if you are trying to get something going like the above using Generics, then I think something like the below might be what you want. Hopefully, it is all reasonably self explanatory so I will just post the code, but feel free to ask questions if there is anything that is not clear.

Unit.java   (pretty basic, nothing special with this one)
package ee.deleyd.design;

public abstract class Unit {
    private String name;

    public Unit(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
}

Open in new window


TemperatureUnit.java    (subclass of above to group these units, and also has some constants for the actual units)
package ee.deleyd.design;

public class TemperatureUnit extends Unit {
    public static final TemperatureUnit CELSIUS = new TemperatureUnit("deg C");
    public static final TemperatureUnit FAHRENHEIT = new TemperatureUnit("deg F");
    
    private TemperatureUnit(String name) {
        super(name);
    }
}

Open in new window


Measurement.java   (here is where the Generics get interesting, but they are really only this complicated so that you can specify the abstract "getvalueAs" method which you seem to really want. It could be made quite a bit cleaner if you forgo having this method defined abstract here, ie. just leave it up to each Measurement subclass to define)
package ee.deleyd.design;

public abstract class Measurement<M extends Measurement<?, U>, U extends Unit> {
    private double value;
    private U unit;
    
    public Measurement(double value, U unit) {
        this.value = value;
        this.unit = unit;
    }
    
    public double getValue() {
        return value;
    }
    
    public U getUnit() {
        return unit;
    }
    
    @Override
    public String toString() {
        return value + " " + unit;
    }

    public abstract M getValueAs(U unit);
}

Open in new window


TemperatureMeasurement.java   (subclass of the above that defines how to do the specific conversions. The "getValueAs" method here is required to fulfil the abstract getValueAs in the above Measurement class and it's parameter and return type are determined by the Type specification in the "extends" spec of the class)
package ee.deleyd.design;

import static ee.deleyd.design.TemperatureUnit.CELSIUS;
import static ee.deleyd.design.TemperatureUnit.FAHRENHEIT;

public class TemperatureMeasurement extends Measurement<TemperatureMeasurement, TemperatureUnit> {
    public TemperatureMeasurement(double value, TemperatureUnit unit) {
        super(value, unit);
    }
    
    @Override
    public TemperatureMeasurement getValueAs(TemperatureUnit unit) {
        if (unit == getUnit()) {
            // No conversion needed
            return this;
        }
        
        if (CELSIUS == unit && FAHRENHEIT == getUnit()) {
            return new TemperatureMeasurement((getValue() - 32) / 9 * 5, CELSIUS);
        } else if (FAHRENHEIT == unit && CELSIUS == getUnit()) {
            return new TemperatureMeasurement(getValue() / 5 * 9 + 32, FAHRENHEIT);
        }
        
        throw new UnsupportedOperationException("Don't know how to convert " + getUnit() + " to " + unit);
    }
}

Open in new window



And a little test class...
package ee.deleyd.design;

import static ee.deleyd.design.TemperatureUnit.CELSIUS;
import static ee.deleyd.design.TemperatureUnit.FAHRENHEIT;

public class XXXTest {
    
    public static void main(String[] args) {
        TemperatureMeasurement temp = new TemperatureMeasurement(25, CELSIUS);
        
        System.out.println("The temp is " + temp);
        
        TemperatureMeasurement convertedTemp = temp.getValueAs(FAHRENHEIT);
        
        System.out.println("The 'converted' temp is " + convertedTemp);
        
        // The following line WON'T compile...
        // TemperatureMeasurement convertedTemp = temp.getValueAs(LengthUnit.METRES);
    }
    
}

Open in new window

0
 
LVL 35

Expert Comment

by:mccarl
Comment Utility
Sorry the last line in the test class above (the line that won't compile) was assuming that you also has the following class...

LengthUnit.java
package ee.deleyd.design;

public class LengthUnit extends Unit {
    public static final LengthUnit METRES = new LengthUnit("m");
    public static final LengthUnit FEET = new LengthUnit("'");
    
    private LengthUnit(String name) {
        super(name);
    }
}

Open in new window


I haven't done a LengthMeasurement class, but you should get the general idea. Let me know if you need help with how you would do it.
0
 
LVL 37

Expert Comment

by:zzynx
Comment Utility
Then I add .getValueAs(Units u) in the Measurement Class (the superclass). And that messes everything up!
Why would you do that?

If your more specific classes LengthMeasurement has
getValueAs(LengthUnits u)

Open in new window

and TemperatureMeasurement has
getValueAs(TemperatureUnits u)

Open in new window


why would you like your Measurement class to have a method
getValueAs(Units u) 

Open in new window

?
0
 

Author Comment

by:deleyd
Comment Utility
My problem seems to boil down to not being able to specify "parameter X is a specific Unit {e.g. Temperature, Length} but can not be "Unit" itself:

double v = m.getValueAs(new Celsius());
double w = getValueAs(new Fahrenheit());
double x = getValueAs(new Unit()) <-- disallowed because it's meaningless. I don't know which unit it is!

Open in new window


with some work I can transform this to the same problem in Generic form:
U expands Unit
getAs(U)
U={Celsius,Fahrenheit}, U != Unit

Open in new window



The reason I wanted to have getValueAs(Units u) in class Measurement was so I could remove duplicate code.

Say I have a GUI displaying a measurement m, and the user wishes to view the measurement in a different unit:
newValue = m.getValueAs(newUnit);

Open in new window


But I also want a type safety check at compile time. Now I have to create a separate version of the code for each measurement type:

TemperatureMeasurement m = new TemperatureMeasurement(23.3, new Celsius());
newValue = m.getValueAs(newTemperatureUnit);

Open in new window

LengthMeasurement m = new LengthMeasurement(12.4, new Inches());
newValue = m.getValueAs(newLengthUnit);

Open in new window


If I have six different measurement types, I end up with six different versions of nearly identical code. (I'm only showing 2 lines here. In my real test application I ended up with six different versions of 4 classes + 2 interfaces.)
0
 
LVL 37

Assisted Solution

by:zzynx
zzynx earned 83 total points
Comment Utility
Now I have to create a separate version of the code for each measurement type
Imo, there's no way around that, since that code - although nearly identical - can never be common for all the different measurement types. It's measurement type specific and hence should be implemented in the specific classes.
0
What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

 
LVL 26

Assisted Solution

by:dpearson
dpearson earned 334 total points
Comment Utility
But I also want a type safety check at compile time.
If you want compile time type checking, then the code is going to have to include some parts that are different (so the compiler can distinguish them).  

If you are happy with runtime type checking, then you can fold the code together into classes which support impossible conversions and detect them at runtime (so Measure.getValueAs() is OK in this model).

Right now I believe you feel that creating a new class "on the fly" (which is essentially what generics are doing for you) is somehow better than creating that same class by giving it a unique name.  If we call either class just "M" then it comes down to

M m = new M() ; // "M" could be a unique name or a complex generic type
m.getValueAs(some units) ;

For the compiler to realize that "some units" is not compatible with M, this means M is a specialized type that does not allow certain units.  However, it's still the same amount of code whether M is either a unique name (e.g. LengthMeasurement) or one specific instantiation of a generic type (e.g. Measurement<TemperatureMeasurement, TemperatureUnit>).  Remember - it can't be  a "generic version of the generic type" (e.g. Measurement<?>) because otherwise you've lost the compile time checking again and you're back to runtime checking.

So I think this really is a choice between 2 equivalent forms and the uniquely named types just seem a lot simpler (at least to me).

Doug
0
 
LVL 35

Assisted Solution

by:mccarl
mccarl earned 83 total points
Comment Utility
Ok, so if you REALLY want your Measurement class to be generic and have as much as possible of the "getValueAs" logic in the base class, then the below is what you could do. Note that I am not advocating this design. It is merely now an academic exercise for me to think of different ways that it "could" be done (not necessarily how it "should" be done). Note the main thing with the code below, is that you move the actual conversion logic (which should be obvious that there HAS to be different code for the different Units somewhere) into the Unit classes themselves.

Unit.java     (the difference here is some template/abstract methods for converting between different units)
package ee.deleyd.design.v2;

public abstract class Unit {
    private String name;

    public Unit(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
    
    public abstract double toStandardUnit(double value);
    public abstract double fromStandardUnit(double value);
}

Open in new window


TemperatureUnit.java      (groups related Temperature units and has nested classes to define all the conversion required. Also has easy to use constants to refer to the individual units)
package ee.deleyd.design.v2;

public abstract class TemperatureUnit extends Unit {
    public static final TemperatureUnit CELSIUS = new CelsiusUnit();
    public static final TemperatureUnit FAHRENHEIT = new FahrenheitUnit();
    public static final TemperatureUnit KELVIN = new KelvinUnit();
    
    private TemperatureUnit(String name) {
        super(name);
    }
    
    private static class CelsiusUnit extends TemperatureUnit {
        
        protected CelsiusUnit() {
            super("deg C");
        }
        
        @Override
        public double toStandardUnit(double value) {
            return value;
        }
        
        @Override
        public double fromStandardUnit(double value) {
            return value;
        }
    }
    
    private static class FahrenheitUnit extends TemperatureUnit {
        
        public FahrenheitUnit() {
            super("deg F");
        }
        
        @Override
        public double toStandardUnit(double value) {
            return (value - 32) / 9 * 5;
        }
        
        @Override
        public double fromStandardUnit(double value) {
            return value / 5 * 9 + 32;
        }
    }
    
    private static class KelvinUnit extends TemperatureUnit {
        
        public KelvinUnit() {
            super("deg K");
        }
        
        @Override
        public double toStandardUnit(double value) {
            return value - 273.15;
        }
        
        @Override
        public double fromStandardUnit(double value) {
            return value + 273.15;
        }
    }
}

Open in new window


Measurement.java    (now the fully generic Measurement looks like this, note the two conversion calls in getValueAs do handle the case when, for example, going F -> K... what happens is that we go from F -> C first and then C -> K. Note that Celsius was chosen arbitrarily as the "standard" unit, it could have been any of them)
package ee.deleyd.design.v2;

public class Measurement<U extends Unit> {
    private double value;
    private U unit;
    
    public Measurement(double value, U unit) {
        this.value = value;
        this.unit = unit;
    }
    
    public double getValue() {
        return value;
    }
    
    public U getUnit() {
        return unit;
    }
    
    @Override
    public String toString() {
        return value + " " + unit;
    }

    public Measurement<U> getValueAs(U unit) {
        return new Measurement<U>(unit.fromStandardUnit(this.unit.toStandardUnit(value)), unit);
    }
}

Open in new window


LengthUnit.java     (just for reference, a LengthUnit would look something like this)
package ee.deleyd.design.v2;

public abstract class LengthUnit extends Unit {
    public static final LengthUnit METRES = new MetresUnit();
    public static final LengthUnit FEET = new FeetUnit();
    
    private LengthUnit(String name) {
        super(name);
    }
    
    private static class MetresUnit extends LengthUnit {
        
        public MetresUnit() {
            super("m");
        }
        
        @Override
        public double toStandardUnit(double value) {
            return value;
        }
        
        @Override
        public double fromStandardUnit(double value) {
            return value;
        }
        
    }
    
    private static class FeetUnit extends LengthUnit {
        
        public FeetUnit() {
            super("'");
        }
        
        @Override
        public double toStandardUnit(double value) {
            return value / 3.28084;
        }
        
        @Override
        public double fromStandardUnit(double value) {
            return value * 3.28084;
        }
    }
}

Open in new window


And the test class now looks like this...
package ee.deleyd.design.v2;

import static ee.deleyd.design.v2.LengthUnit.FEET;
import static ee.deleyd.design.v2.TemperatureUnit.CELSIUS;
import static ee.deleyd.design.v2.TemperatureUnit.FAHRENHEIT;
import static ee.deleyd.design.v2.TemperatureUnit.KELVIN;


public class XXXTest {
    
    public static void main(String[] args) {
        Measurement<TemperatureUnit> temp = new Measurement<TemperatureUnit>(25, CELSIUS);
        
        System.out.println("The temp is " + temp);
        
        Measurement<TemperatureUnit> convertedTemp = temp.getValueAs(FAHRENHEIT);
        
        System.out.println("The temp in 'deg F' is " + convertedTemp);
        
        System.out.println("And in Kelvin (from Celsius) " + temp.getValueAs(KELVIN));
        System.out.println("And in Kelvin (from Fahrenheit) " + convertedTemp.getValueAs(KELVIN));
        
        // The following line WON'T compile...
        // Measurement<TemperatureUnit> convertedTemp2 = temp.getValueAs(FEET);
        
        // The following is allowed, however...
        // This maybe bad or good, depending on how you look at it
        Measurement<Unit> anotherMeasurement = new Measurement<Unit>(42, CELSIUS);
        Measurement<Unit> anotherMeasurementConverted = anotherMeasurement.getValueAs(FEET);
    }
}

Open in new window


Note the code in the last two lines. As demonstrated, it is allowable to make a Measurement that can take in any Unit and therefore allow a conversion that doesn't make sense. As mentioned this can be good or bad. This allows flexibility in that you could leave the Unit resolution until runtime (and then you can still perform some runtime sanity checking on the units used) while also allowing for type safety if the programmer uses the right type arguments for Measurement.

If you don't like the above flexibility, then your only option to disallow instantiation of the Measurement class directly and then define concrete subclass the "tie down" the type argument that is used, such as...
public class TemperatureMeasurement extends Measurement<TemperatureUnit> {   // ...

Open in new window

0
 

Author Comment

by:deleyd
Comment Utility
A "code generator" program I'm back to. I have a template:
public void setUnits(<insertUnitTypeHere> units);

Open in new window

and I tell the code generator program what I want it to put for <insertUnitTypeHere>.

So I tell it to make me some code for TemperatureUnits, and more code for LengthUnits, etc.

I'm wondering if this "code generator" falls under the heading of Domain Specific Language (as in Martin Fowler's book).

And I'm wondering why theoretically I have to go this code generation route? That's what I'm really interested in. Writing the code generator is easy (actually it was my original solution before embarking on this looking for an alternative). Would it be possible to extend the Java language, or design a new general purpose language in the future which doesn't have that limitation?

(I keep thinking of that Mars probe which failed because there was a confusion in the code between Metric units and English units. We should be able to write code that doesn't allow for this confusion, and which catches it at compile time [as it's no use if it catches it at run time while it's landing on Mars].)
0
 
LVL 35

Expert Comment

by:mccarl
Comment Utility
We should be able to write code that doesn't allow for this confusion, and which catches it at compile time
Yes, you can, and multiple people here HAVE and have given you multiple approaches.

You haven't made any comments on the code that I have posted (nor on Doug's comments). It would be appreciated if you could. I mean we have taken the time to put out thoughts down in the above comments!
0
 
LVL 26

Assisted Solution

by:dpearson
dpearson earned 334 total points
Comment Utility
+1 to mccarl's comment above :)

But while we're at it:
Would it be possible to extend the Java language, or design a new general purpose language in the future which doesn't have that limitation?
You should check out Scala (http://www.scala-lang.org/).  It runs on the JVM and supports interoperability with Java and it's very good at Domain Specific Languages (together with lots of other things).  It's also too complex for me to ever want to use in production :)

Doug
0
 

Author Comment

by:deleyd
Comment Utility
I've been in hospital for the past few days with a deadly infection. I'll get back to this soon.
0
 
LVL 26

Expert Comment

by:dpearson
Comment Utility
Yikes!  Hope you feel better soon - no rush to get back to this stuff...it'll keep.

Doug
0
 

Author Closing Comment

by:deleyd
Comment Utility
I hope next week to get back into working on this. I've been studying Java Enums, noting that Enum is actually a Class, far more advanced than simple C enums. I think I might be able to get the behavior I want by using Enum classes.

Thank you for the help everyone!
0

Featured Post

How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

Join & Write a Comment

Suggested Solutions

Title # Comments Views Activity
topping2 challenge 13 55
HashMap Vs TreeMap 12 47
What is the latest versions eclipse neon 2 113
Strange loading of website behaviour 3 22
Dependencies in Software Design In software development, the idea of dependencies (http://en.wikipedia.org/wiki/Coupling_%28computer_programming%29) is an issue of some importance. This article seeks to explain what dependencies are and where they …
Introduction This article explores the design of a cache system that can improve the performance of a web site or web application.  The assumption is that the web site has many more “read” operations than “write” operations (this is commonly the ca…
Viewers learn about the third conditional statement “else if” and use it in an example program. Then additional information about conditional statements is provided, covering the topic thoroughly. Viewers learn about the third conditional statement …
This tutorial explains how to use the VisualVM tool for the Java platform application. This video goes into detail on the Threads, Sampler, and Profiler tabs.

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now