【问题标题】:Is it a good way to implement Distance with diffrent units用不同单位实现距离的好方法吗
【发布时间】:2016-05-11 19:50:20
【问题描述】:

我一直在寻找一些可以用不同单位表示距离的好模式。我找到了Martin Fowler article about quantities,我编写了如下代码:

这是距离类(我认为没有必要将其抽象化):

public class Distance {
    double mValue;
    DistanceUnit mUnit;

    public Distance(double value, DistanceUnit unit){
        this.mValue = value;
        this.mUnit = unit;
    }

    public Distance toUnit(DistanceUnit unit){
        double factor = this.mUnit.getMetresFactor()/unit.getMetresFactor();
        double newValue = this.mValue * factor;
        Distance distance = new Distance(newValue, unit);
        return distance;
    }

    @Override
    public String toString(){
        return String.valueOf(mValue);
    }
}

看起来很简单。转换toUnit 是基于DistanceUnit 方法getMetresFactor。每个 Unit 类都实现了DistanceUnit 接口,并拥有getMetresFactor() 方法,如:

public interface DistanceUnit {
    double getMetresFactor();
}


public class Inch implements DistanceUnit {
    @Override
    public double getMetresFactor() {
        return 0.0254;
    }
}


public class Kilometer implements DistanceUnit {
    @Override
    public double getMetresFactor() {
        return 1000.0;
    }
}

而用法例如:

Distance inches = new Distance(300.0, new Inch());
Distance kilometres = inches.toUnit(new Kilometres());

所以它返回正确的值。

以这种方式存储距离的好方法吗?也许您知道这种方法的一些弱点。也许在这里使用 FactoryMethod 模式来构建基于单位快捷方式的距离是一个好主意,例如米的“m”。如果我有很多单位,我会考虑课程的数量......让工厂根据单位名称返回米的系数是个好主意吗?那么单元就没有班级了吗?

【问题讨论】:

  • 与其重新发明轮子,你有没有想过使用像 JScience 这样已经存在的库?
  • 我期待某个地方会像 JScience 这样的东西,但我没有听说过它,我还没有找到它。我会看看。但我的问题也是,这是否是像我一样做到这一点的好方法。我不是很有经验的程序员,所以我的每一个好词都很有价值。
  • @Tim Biegeleisen 喜欢他可以使用自己的距离。如果重新发明轮子比使用现有的坏轮子更简单更快,那么重新发明轮子是没有问题的

标签: java design-patterns distance


【解决方案1】:

嗯,我会使用枚举而不是 DistanceUnit 类,因为它们没有不同的实例。 您可以将值设置为枚举like here

然后调用 enum.getValue() 而不是 unit.getMetresFactor()。 也有点令人困惑,mValue 值是米还是距离单位,如果是米,你必须有

double factor =  unit.getMetresFactor();

那里 好的,现在支持任何转换功能:

import java.util.HashMap;
import java.util.Map;


public abstract class MeasureConverter {

public abstract double valueToBasic(double value);

public abstract double basictoValue(double basic);

/**
 *
 */
public static Map<String, MeasureConverter> converters;

public static Map<String, MeasureConverter> getConverters() {
    if (converters == null) {
        converters = new HashMap<String, MeasureConverter>();

        converters.put("kilo", new MeasureConverter() {

            @Override
            public double valueToBasic(double value) {
                return value * 1000;
            }

            @Override
            public double basictoValue(double basic) {
                return basic / 0.001;
            }
        });

        // taking the basic temperature value in kelvines
        converters.put("kelvine", new MeasureConverter() {

            @Override
            public double valueToBasic(double value) {
                return value;
            }

            @Override
            public double basictoValue(double basic) {
                return basic;
            }
        });

        converters.put("celsius", new MeasureConverter() {

            @Override
            public double valueToBasic(double value) {
                return value + 273.15;
            }

            @Override
            public double basictoValue(double basic) {
                return basic - 273.15;
            }
        });

        converters.put("faren", new MeasureConverter() {

            @Override
            public double valueToBasic(double value) {
                return value * 1.8 - 459.67 ; // or whatever is there?
            }

            @Override
            public double basictoValue(double basic) {
                return (basic + 459.67  ) / 1.8;// or whatever is there?
            }
        });

    }

    return converters;
}

}

然后:

import java.util.Objects;


public class MeasurePattern {

double value;
String name;

public MeasurePattern(double value, String name) {
    this.value = value;
    this.name = name;
}

@Override
public String toString() {
    return "MeasurePattern{" + "value=" + value + ", name=" + name + '}';
}

@Override
public int hashCode() {
    int hash = 7;
    hash = 29 * hash + (int) (Double.doubleToLongBits(this.value) ^ (Double.doubleToLongBits(this.value) >>> 32));
    hash = 29 * hash + Objects.hashCode(this.name);
    return hash;
}

@Override
public boolean equals(Object obj) {
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    final MeasurePattern other = (MeasurePattern) obj;
    if (Double.doubleToLongBits(this.value) != Double.doubleToLongBits(other.value)) {
        return false;
    }
    if (!Objects.equals(this.name, other.name)) {
        return false;
    }
    return true;
}

public MeasurePattern convertTo(String converter) {

    MeasureConverter mycon = MeasureConverter.getConverters().get(name);
    MeasureConverter hiscon = MeasureConverter.getConverters().get(converter);

    double basic = mycon.valueToBasic(value);
    double hisValue = hiscon.basictoValue(basic);
    return new MeasurePattern(hisValue, converter);

}

public static void main(String[] args) {
    //trying temperatures;

    MeasurePattern temp = new MeasurePattern(10, "celsius");

    MeasurePattern kelvine = temp.convertTo("kelvine");
    MeasurePattern faren = kelvine.convertTo("faren");
    MeasurePattern cels = faren.convertTo("celsius");

    System.out.println("kelvine = " + kelvine);
    System.out.println("faren = " + faren);
    System.out.println("cels = " + cels);

}

}

输出:

kelvine = MeasurePattern{value=283.15, name=kelvine}
faren = MeasurePattern{value=412.67777777777775, name=faren}
cels = MeasurePattern{value=9.999999999999943, name=celsius}

【讨论】:

  • 但如果有人想添加新单元,他必须编辑枚举类?所以我需要将所有可能的单位添加到枚举?
  • 是的,我认为编辑枚举并没有那么错。在现实世界中。如果您真的不想修改...啊,您可以创建距离单位值的 map,例如 HashMap dims;你放哪里像 dims.put("kilometer", 0.001); dims.put("inch",
  • 好的。谢谢。我懂了。我仍然在想 Tim Biegeleisen 展示的 lib。使用它而不是重写它不是更好吗? lib真的这么慢吗?
  • 我想我会完全避免这个因素。通过温度转换(例如 F 到 C),您还必须添加基本偏移量 (32)。也许其他系统具有非线性因素或需要其他信息。
  • @JJ 也许知道你的库中的一切(而你自己的库肯定会有非预期的行为),但如果其他人必须使用你的代码怎么办?开源库可能已经为人所知、有更好的文档记录、更好的测试和更快的修复......
【解决方案2】:

您可以将其与java.util.concurrent.TimeUnit 类似地实现为枚举。例如

public enum DistanceUnit {

  KILOMETER {
    @Override
    protected double conversionFactor(DistanceUnit toDistanceUnit) {
      switch (toDistanceUnit) {
        case KILOMETER:
          return 1;
        case MILE:
          return 0.621371;
        default:
          throw new UnsupportedOperationException(toDistanceUnit + " is not supported");
      }
    }
  },

  MILE {
    @Override
    protected double conversionFactor(DistanceUnit toDistanceUnit) {
      switch (toDistanceUnit) {
        case KILOMETER:
          return 1.60934;
        case MILE:
          return 1;
        default:
          throw new UnsupportedOperationException(toDistanceUnit + " is not supported");
      }
    }
  };

  public double toDistance(double value, DistanceUnit targetDistance) {
    return value * conversionFactor(targetDistance);
  }

  protected abstract double conversionFactor(DistanceUnit toDistanceUnit);

}

将您的 Distance 类更改为

public class Distance {
    double mValue;
    DistanceUnit mUnit;

    public Distance(double value, DistanceUnit unit){
        this.mValue = value;
        this.mUnit = unit;
    }

    public Distance toUnit(DistanceUnit unit){
        double newValue = mUnit.toDistance(mValue, unit);
        Distance distance = new Distance(newValue, unit);
        return distance;
    }

    @Override
    public String toString(){
        return String.valueOf(mValue);
        }
}

而且客户端代码看起来会很清晰

public class Main {

  public static void main(String[] args) {
    Distance kilometers = new Distance(265.35, DistanceUnit.KILOMETER);
    Distance miles = kilometers.toUnit(DistanceUnit.MILE);
    System.out.println(miles);
  }
}

会输出

164.88079485000003

【讨论】:

    【解决方案3】:

    Java 约定不使用 m(ember) 前缀(而是使用 this. 限定符),并且在 java 中非常重视约定(例如与 C++ 相对)。

    toString 错过了单位。

    JScience 提供了更多,以不同单位计算的能力,m/s²,等等。你的类是一个很好的抽象。但在更广泛的背景下,您可能需要数学运算、单位幂(-2 表示上面的 s)。

    先看看自己的使用思路:

    (只是垃圾:)

    U speedUnit = U.of(Distance::km, Time::h.up(-1));
    double timeInS = U.mile(40).div(speedunit(30)).in(U.m);
    

    【讨论】:

    • 是的,我明白了,我这样做是因为我在将它们相乘时有点困惑。所以我看到在我的问题中我需要编写很多已经实现的程序。
    • 否则不会很有趣?不妨试试 JScience 的例子,看看你能不能把它做得更好。他们有别名(如果我没记错的话),比如s-1的Hz等等。
    【解决方案4】:

    我认为你应该使用“策略”模式。

    一个接口:

     public interface DistanceUnit {
         double getDistance(int metres);
     }
    

    英寸类:

    public class Inch implements DistanceUnit {
        @Override
        public double getDistance(int metres) {
           return meters*39; //do conversion here
        }
    }
    

    公里类:

    public class Kilometres implements DistanceUnit {
        @Override
        public double getDistance(int metres) {
            return meters/1000; //do conversion here
        }
    }
    

    然后:

    List<DistanceUnit> distanceList = new ArrayList<>();
    distanceList.add(new Inch());
    distanceList.add(new Kilometres());
    
    
    for (DistanceUnit item : distanceList) {
        System.out.println(item.getDistance(1000));
    }
    

    如果我理解你,我认为这是一个简单而干净的解决方案。

    您可以按照此模型在其他单位之间进行转换。

    【讨论】:

    • 我不认为这是一个很好的解决方案。如果我想将公里转换为英寸?所以它不是自动化的。
    • 您可以拥有不同的层次结构并独立维护它们。我认为创建更多的类和编写更少的代码总是更好。
    • PS:如果将来发明了新的单位距离,您必须只添加新的转换层次结构。解耦很好。
    猜你喜欢
    • 2015-08-14
    • 1970-01-01
    • 1970-01-01
    • 2011-04-17
    • 1970-01-01
    • 2013-02-21
    • 1970-01-01
    • 1970-01-01
    • 2011-11-18
    相关资源
    最近更新 更多