通用向量类继承非通用线段类

3
我正在尝试编写一个可以表示力和维度的向量类。通常情况下,这不是问题,因为大多数人会创建一个名为“Magnitude”的属性,返回一个双精度浮点型。双精度浮点型并不关心它们所代表的是力还是维度,都能很好地代表。然而,我正在使用这个开源库Unit Class Library来表示维度和力。

以下是我想要实现的代码,是否有办法使属性根据T返回不同类型(构造函数也是同样的情况)?如果可能,应该如何实现?

public class Vector<T> : LineSegment
{
    ForceUnit _magnitudeForce;

    /// <summary>
    /// Returns the magnitude of the vector (equals the length of the line segment parent if T is Dimension, if T is Force, return variable )
    /// </summary>
    public T Magnitude
    {
        get {

            Type typeT = typeof(T);

            if (typeT == typeof(Dimension))
            {
                return base.Length;
            }
            else if (typeT == typeof(ForceUnit))
            {
                return _magnitudeForce;
            }

        ...
    }

我可以使用dynamic关键字来实现这个目的吗?或者,我应该创建一组接口,然后在Unit Library上使用它们吗?

interface IDistance
{
    double Centimeters { get; }
    double Feet { get; }
    double Inches { get; }
    double Kilometers { get; }
    double Meters { get; }
    double Miles { get; }
    double Millimeters { get; }
    double Sixteenths { get; }
    double ThirtySeconds { get; }
    double Yards { get; }
}

interface IForceUnit
{
    double Kips { get; }
    double Newtons { get; }
    double Pounds { get; }
}

然后创建一个Hybrid类,实现这两个类的功能,作为该类的大小。

我管理Unit Class Library,所以欢迎提出在此进行更改的建议。但请记住,该库的直观性和单位转换能力是我想保持的项目价值。


你当然可以这样做,但这需要是通用的吗?在我看来,如果你使用一堆if语句或switch语句检查typeof(T),那么你的方法/类/属性根本不应该是通用的。 - Sriram Sakthivel
请查看问题中链接的项目。 - jth41
任何静态定义单位的系统都注定会失败。例如,当你需要表示每秒钟的角度时会发生什么呢?你不可能硬编码所有可能的单位组合。该系统需要从基本单位推导出单位。 - John Alexiou
如果所有的单位都是从IUnit派生出来并且具有一些共同属性(比如它们的值),那么这将是微不足道的。我建议放弃这个单位库,寻找一个更好的。 - John Alexiou
我不确定我理解你所说的使用接口来进行单元测试。你能详细说明一下吗? - jth41
显示剩余2条评论
4个回答

2
你绝对不想进行那种类型检查 - 更好的做法是创建一个抽象基类或接口,然后从其派生。
例如,如果你使用一个抽象类:
public abstract class Vector<T> : LineSegment
{
    public abstract T Magnitude { get; }
}

public class DimensionVector : Vector<Dimension>
{
    public override Dimension Magnitude { get { return base.Length; } }
}

public class ForceUnitVector : Vector<ForceUnit>
{
    ForceUnit magnitudeForce;

    public override ForceVector Magnitude { get { return this.magnitudeForce; } }
}

2

让我提供一种替代的单位库。这将允许您不必处理泛型并可以使用派生单位。这是第一个示例:

class Program
{
    static void Main(string[] args)
    {
        Vector A=new Vector(Unit.Foot, 0.3, 0.5, 0.7, 1.0);
        Vector B=A.ConvertTo(Unit.Inch);
        Vector C=B*B; // Convert to square inches, compatible with SI units of m^2

        Debug.WriteLine(A.ToString());  // [0.3,0.5,0.7,1]
        Debug.WriteLine(B.ToString());  // [3.6,6,8.4,12]
        Debug.WriteLine(C.ToString());  // [12.96,36,70.56,144]

        Vector F=new Vector(Unit.PoundForce, 100.0, 130.0, 150.0, 180.0);

        Vector K=F/B;   // Stiffness is force per distance, compatible with SI units of kg/m^2
        Vector P=F/C;   // Pressure is force per area, compatible with SI units kg/(m*s^2)

        Debug.WriteLine(F.ToString());  // [100,130,150,180]
        Debug.WriteLine(K.ToString());  // [27.78,21.67,17.86,15]
        Debug.WriteLine(P.ToString());  // [7.716,3.611,2.126,1.25]

        var x=3*Unit.Foot.FactorTo(Unit.Inch);  // x=36 inches

    }
}

Unit类是:

public class Unit : IEquatable<Unit>
{
    readonly int M, L, T; // base unit powers. For example Area: (M=0, L=2, T=0)
    readonly double x; // base unit factor. For example 1 km = (1000)*m

    public Unit(int M, int L, int T, double factor)
    {
        this.M=M;
        this.L=L;
        this.T=T;
        this.x=factor;
    }
    public Unit(Unit other)
    {
        this.M=other.M;
        this.L=other.L;
        this.T=other.T;
        this.x=other.x;
    }
    public int[] Dimension { get { return new int[] { M, L, T }; } }
    public double Factor { get { return x; } }
    public bool IsConvertibleTo(Unit other) { return M==other.M&&L==other.L&&T==other.T; }
    public double FactorTo(Unit target)
    {
        if(IsConvertibleTo(target))
        {
            return Factor/target.Factor;
        }
        throw new ArgumentException("Incompatible units in target.");
    }
    #region IEquatable Members

    /// <summary>
    /// Equality overrides from <see cref="System.Object"/>
    /// </summary>
    /// <param name="obj">The object to compare this with</param>
    /// <returns>False if object is a different type, otherwise it calls <code>Equals(Unit)</code></returns>
    public override bool Equals(object obj)
    {
        if(obj is Unit)
        {
            return Equals((Unit)obj);
        }
        return false;
    }

    /// <summary>
    /// Checks for equality among <see cref="Unit"/> classes
    /// </summary>
    /// <param name="other">The other <see cref="Unit"/> to compare it to</param>
    /// <returns>True if equal</returns>
    public bool Equals(Unit other)
    {
        return M==other.M
            &&L==other.L
            &&T==other.T
            &&x.Equals(other.x);
    }

    /// <summary>
    /// Calculates the hash code for the <see cref="Unit"/>
    /// </summary>
    /// <returns>The int hash value</returns>
    public override int GetHashCode()
    {
        return (((17*23+M.GetHashCode())*23+L.GetHashCode())*23+T.GetHashCode())*23+x.GetHashCode();
    }

    #endregion
    public override string ToString()
    {
        return string.Format("{0}(M:{1},L:{2},T:{3})", Factor, M, L, T);
    }
    public static Unit operator*(double relative, Unit unit)
    {
        return new Unit(unit.M, unit.L, unit.T, relative*unit.Factor);
    }
    public static Unit operator/(Unit unit, double divisor)
    {
        return new Unit(unit.M, unit.L, unit.T, unit.Factor/divisor);
    }

    public static Unit operator*(Unit unit, Unit other)
    {
        return new Unit(
            unit.M+other.M,
            unit.L+other.L,
            unit.T+other.T,
            unit.Factor*other.Factor);
    }
    public static Unit operator/(Unit unit, Unit other)
    {
        return new Unit(
            unit.M-other.M,
            unit.L-other.L,
            unit.T-other.T,
            unit.Factor/other.Factor);
    }

    public static Unit operator^(Unit unit, int power)
    {
        return new Unit(
            unit.M*power,
            unit.L*power,
            unit.T*power,
            Math.Pow(unit.Factor, power));
    }

    public static readonly Unit Meter=new Unit(0, 1, 0, 1.0);
    public static readonly Unit Millimeter=0.001*Meter;
    public static readonly Unit Inch=25.4*Millimeter;
    public static readonly Unit Foot=12*Inch;
    public static readonly Unit Yard=3*Foot;

    public static readonly Unit Second=new Unit(0, 0, 1, 1.0);
    public static readonly Unit Minute=60*Second;
    public static readonly Unit Hour=60*Minute;

    public static readonly Unit Kilogram=new Unit(1, 0, 0, 1.0);
    public static readonly Unit PoundMass=0.453592*Kilogram;
    public static readonly Unit Newton=Kilogram*(Meter/(Second^2));
    public static readonly Unit PoundForce=4.44822162*Newton;
}

用于Vector类的使用

public class Vector 
{

    public Vector(Unit unit, int size)
    {
        this.Elements=new double[size];
        this.Unit=unit;
    }
    public Vector(Unit unit, params double[] values)
    {
        this.Unit=unit;
        this.Elements=values;
    }

    public Unit Unit { get; private set; }
    public double[] Elements { get; private set; }

    public double this[int index] { get { return Elements[index]; } }
    public int Count { get { return Elements.Length; } }

    public Vector ConvertTo(Unit target)
    {
        double factor=Unit.FactorTo(target);
        double[] values=Array.ConvertAll(Elements, (x) => factor*x);
        return new Vector(target, values);
    }

    public override string ToString()
    {
        string[] items=Array.ConvertAll(Elements, (x) => x.ToString());

        return string.Format("[{0}] in {1}", string.Join(",", items), Unit.ToString());
    }

    public static Vector operator+(Vector x, Vector y)
    {
        if(x.Count==y.Count)
        {
            x=x.ConvertTo(y.Unit);

            double[] result=new double[x.Count];
            for(int i=0; i<result.Length; i++)
            {
                result[i]=x[i]+y[i];
            }
            return new Vector(y.Unit, result);
        }
        throw new IndexOutOfRangeException("Vectors must have the same number of elements.");
    }
    public static Vector operator-(Vector x, Vector y)
    {
        if(x.Count==y.Count)
        {
            x=x.ConvertTo(y.Unit);

            double[] result=new double[x.Count];
            for(int i=0; i<result.Length; i++)
            {
                result[i]=x[i]-y[i];
            }
            return new Vector(y.Unit, result);
        }
        throw new IndexOutOfRangeException("Vectors must have the same number of elements.");
    }
    public static Vector operator*(double x, Vector y)
    {
        double[] result=new double[y.Count];
        for(int i=0; i<result.Length; i++)
        {
            result[i]=x*y[i];
        }
        return new Vector(y.Unit, result);
    }
    public static Vector operator*(Vector x, Vector y)
    {
        if(x.Count==y.Count)
        {
            double[] result=new double[x.Count];
            for(int i=0; i<result.Length; i++)
            {
                result[i]=x[i]*y[i];
            }
            return new Vector(x.Unit* y.Unit, result);
        }
        throw new IndexOutOfRangeException("Vectors must have the same number of elements.");
    }
    public static Vector operator/(Vector x, Vector y)
    {
        if(x.Count==y.Count)
        {
            double[] result=new double[x.Count];
            for(int i=0; i<result.Length; i++)
            {
                result[i]=x[i]/y[i];
            }
            return new Vector(x.Unit/y.Unit, result);
        }
        throw new IndexOutOfRangeException("Vectors must have the same number of elements.");
    }
}

当然,“Unit”类需要扩展以包括更多的内置单位。角度测量没有单位,因此还应该有“static readonly Unit Radian = new Unit(0,0,0,1.0);”和“Degree = 180/Math.PI*Radian”。 - John Alexiou
此外,单位指数(M,L,T 值)应该是有理数,因此需要开发一个完整的 Fraction 类来代替 int,以正确处理具有有理指数的单位,例如 L=3/2 - John Alexiou
我有一些其他问题要询问关于这个实现的,可能过于详细以至于无法在评论中解决,你能加入到这个 聊天室 和我一起讨论吗? - jth41
你能否给我更多关于你的 FactorToIsConvertableTo 方法的见解?似乎 IsConvertable 应该重命名为 IsSameUnitsAs。而 FactorTo 似乎只是除法。 - jth41
在查看了林书豪的回答之后,似乎他的回答更简单,并且更贴近SOLID原则,即每个类只有一个职责。那么他的回答有什么缺点吗? - jth41
显示剩余5条评论

2
为什么不直接从Vector派生,然后丢弃作为Dimension的父级Magnitude值呢?
就像这样?
public class Load:Vector
{

    private ForceUnit _magnitude;

    public ForceUnit Magnitude
    {
        get
        {
                return this._magnitude;          
        }
    }



    public PointLoad(ForceUnit magnitudeOfLoad, Vector directionVector)
        : base(...)
    {
        _magnitude = magnitudeOfLoad;
    }

}

这种简单方法有没有我没注意到的缺点? - jth41
我看到的主要缺点是你必须坚持预定义的单位。在这个方案中不允许使用派生单位。 - John Alexiou

1

你可以返回一个对象或动态类型,并进行强制转换。

然而,我建议你不要这样做,因为这没有意义。我建议你重新审视你的逻辑。

public object Magnitude
    {
        get {

            Type typeT = typeof(T);

            if (typeT == typeof(Dimension))
            {
                return base.Length;
            }
            else if (typeT == typeof(ForceUnit))
            {
                return _magnitudeForce;
            }

        ...
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接