【问题标题】:proper class hierarchy for 2D and 3D vectors2D 和 3D 向量的正确类层次结构
【发布时间】:2011-06-14 00:03:44
【问题描述】:

我想要一个通用的向量抽象类/特征来指定某些方法,例如:

trait Vec 
{
  def +(v:Vec):Vec
  def *(d:Double):Vec

  def dot(v:Vec):Double
  def norm:Double
}

我想要Vec2DVec3D 扩展Vec

class Vec2D extends Vec { /* implementation */ }
class Vec3D extends Vec { /* implementation */ }

但是,例如,我怎样才能使Vec2D 只能添加到其他Vec2D 而不能添加到Vec3D

现在我只是在没有共同的Vec 祖先的情况下实现Vec2DVec3D,但是重复代码会变得乏味。我必须实现所有依赖于这些类的几何类(例如TrianglePolygonMesh、...)两次,一次用于Vec2D,另一次用于Vec3D

我看到了 java 实现:javax.vecmath.Vector2djavax.vecmath.Vector3d 没有共同的祖先。这是什么原因?有没有办法在scala中克服它?

【问题讨论】:

    标签: oop scala architecture hierarchy


    【解决方案1】:

    你可以使用自我类型:

    trait Vec[T] { self:T =>
      def +(v:T):T
      def *(d:Double):T
    
      def dot(v:T):Double
      def norm:Double
    }
    
    class Vec2D extends Vec[Vec2D] { /* implementation */ }
    class Vec3D extends Vec[Vec3D] { /* implementation */ }
    

    但如果两个实现非常相似,您也可以尝试对维度进行抽象。

    sealed trait Dimension
    case object Dim2D extends Dimension
    case object Dim3D extends Dimension
    
    sealed abstract class Vec[D <: Dimension](val data: Array[Double]) { 
    
      def +(v:Vec[D]):Vec[D] = ...
      def *(d:Double):Vec[D] = ...
    
      def dot(v:Vec[D]):Double = ...
      def norm:Double = math.sqrt(data.map(x => x*x).sum)
    }
    
    class Vec2D(x:Double, y:Double) extends Vec[Dim2D.type](Array(x,y))
    class Vec3D(x:Double, y:Double, z:Double) extends Vec[Dim3D.type](Array(x,y,z))
    

    当然,这取决于您希望如何表示数据,以及您是否希望拥有可变或不可变的实例。对于“现实世界”应用程序,您应该考虑http://code.google.com/p/simplex3d/

    【讨论】:

    • 自我类型允许您引用this,而达里奥答案中的CRTP模式则不允许。
    • @dsg:你的意思是你不能用 CRTP 引用this
    【解决方案2】:

    作为requested,设计基本特征的最有用方法涉及CRTPself-type annotation

    trait Vec[T <: Vec[T]] { this: T =>
      def -(v: T): T
      def *(d: Double): T
    
      def dot(v: T): Double
      def norm: Double = math.sqrt(this dot this)
      def dist(v: T) = (this - v).norm
    }
    

    没有自我类型,就不可能调用this.dot(this),因为dot 需要T;因此我们需要使用注解来强制执行。

    另一方面,如果没有 CRTP,我们将无法在 (this - v) 上调用 norm,因为 - 返回一个 T,因此我们需要确保我们的类型 T 具有此方法,例如声明T 是一个 Vec[T]

    【讨论】:

      【解决方案3】:

      我不确定正确的 Scala 语法,但您可以实现 CRTP,即通过泛型参数定义实际类型。

      trait Vec[V <: Vec[V]] {
        def +(v:V):V
        ...
      }
      
      class Vec2D extends Vec[Vec2D] { }
      class Vec3D extends Vec[Vec3D] { }
      
      class Polygon[V <: Vec[V]] {
        ...
      }
      

      【讨论】:

      【解决方案4】:

      在 JVM 上具有与 CRTP 模式的共同祖先存在一个大问题。当您使用不同的实现执行相同的抽象代码时,JVM 将取消优化代码(无内联 + 虚拟调用)。如果您只使用 Vec3D 进行测试,您不会注意到这一点,但如果您同时使用 Vec2D 和 Vec3D 进行测试,您会看到性能大幅下降。此外,逃逸分析不能应用于去优化代码(没有标量替换,没有新实例的限制)。 缺少这些优化将使您的程序减慢 3 倍(非常全面的猜测取决于您的代码)。

      尝试一些运行大约 10 秒的基准测试。在同一个运行测试中,使用 Vec2D,然后是 Vec3D,然后是 Vec2D,然后是 Vec3D。你会看到这种模式:

      • Vec2D ~10 秒
      • Vec3D ~30 秒
      • Vec2D ~30 秒
      • Vec3D ~30 秒

      【讨论】:

        猜你喜欢
        • 2015-05-14
        • 2013-11-21
        • 1970-01-01
        • 2015-11-01
        • 1970-01-01
        • 2014-01-30
        • 1970-01-01
        • 1970-01-01
        • 2018-02-13
        相关资源
        最近更新 更多