patternjavaMinor
Calculating length with different units
Viewed 0 times
unitswithlengthdifferentcalculating
Problem
We have an exercise to design a simple library, which can calculate some length with different unit, e.g.
and the result can be any of:
I want to design it in Object-oriented, and have a solution like this:
It works, but I have several questions:
These questions are mostly based on this possible requirement change: don't want to support
Update: from this article, seems it's not a good idea to use
2m*2 + 20cm/2 - 5mmand the result can be any of:
4.095m
409.5cm
4095mmI 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?
Unithas a methodtoMMwhich is bind to a concrete type of unitmm(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?
Lengthhas chosenmmfor 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?Solution
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
It could look like:
This also avoids having to arbitrarily choose a "base" unit (mm in your example).
Finally I would suggest defining an interface for Unit:
Then your enum would be:
This will allow a more flexible design where you can add units by implementing the interface or use the provided standard units.
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.
Code Snippets
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
}public interface Unit {
BigDecimal toMeters(BigDecimal length);
//and possibly additional helper methods:
default BigDecimal convert(BigDecimal length, Unit sourceUnit) { ... }
}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); }
}Context
StackExchange Code Review Q#112592, answer score: 6
Revisions (0)
No revisions yet.