【问题标题】:Constructing subclasses from base abstract class从基本抽象类构造子类
【发布时间】:2016-11-08 09:49:15
【问题描述】:

我想在将创建具体子类的抽象类中定义一个构造函数。

abstract class A {
  type Impl <: A
  def construct() : Impl = {
    val res = new Impl() //compile error: class type required but A.this.Impl found
    // do more initialization with res
  }
}

class B extends A {type Impl = B}
class C extends A {type Impl = C}
//...
val b = new B
b.construct() // this should create a new instance of B

这里有什么问题?这甚至可以实施吗? 编辑: 澄清:我想对构造方法进行抽象。我不想从子类或伴随对象中分别调用 new Bnew C

【问题讨论】:

  • 避免调用 new B 或 new C 有什么好处?
  • 我可能有很多子类(B、C、D、...),我想避免重复/样板代码。
  • 唯一能做你想做的事就是使用反射。正如我在回答中所说,这不是一个好主意。
  • 如果你有很多子类型都在做同样的初始化,你可能有糟糕的设计。一般来说,为什么不直接调用A的构造函数呢?像这样:class B extends A(param1, ...) { ... }。那么A 将是一个适当的抽象,而不仅仅是一些hack。
  • 我想要做的是最终在不知道具体类的情况下从类 A 中的方法调用construct()。所以我不能调用 new B()。这就是我试图用 new Impl() 做的事情,但在这里我得到了编译器错误。此错误类似于使用参数化类型(类型擦除)时遇到的错误。所以我认为使用带有清单的反射可能会解决问题 - 根据@Monkey的回答

标签: scala abstract-class abstract-type


【解决方案1】:

如果要创建一个新实例,则需要显式调用构造函数。

abstract class A {

  def newInstance(): this.type;

  def construct() : this.type = {
    val res = newInstance()
  }
}

class B extends A {
  def newInstance() = new B()
}

Scala 在运行时擦除类型,因此在创建类时无法知道 Impl 的含义。

【讨论】:

  • 这不起作用。您将在new B() 上收到编译错误,因为 this.type 是单例类型 - 实例本身的类型与 B 类型不同。您不能创建单例类型的对象。
【解决方案2】:

您可以将构造函数放在伴随对象中,而不是放在抽象类中。像这样:

object A {
  def apply(i:Int):A = new B(...)
  def apply(s:String):A = new C(...)
}

现在,您可以通过调用A(42)A("foobar") 来创建A 的实例。当然,字符串和整数参数只是示例。如果所有构造函数的参数具有相同的类型,则此重载将不起作用。在这种情况下,您可以轻松地创建不同的方法并将它们称为 apply 以外的名称。

【讨论】:

    【解决方案3】:

    您可以使用反射来创建一个新实例。像这样的东西会起作用,但在我看来不值得麻烦。一方面,您只能检查运行时是否存在合适的构造函数。

    def newInstance[T:ClassManifest]:T = {
      val klass = implicitly[ClassManifest[T]].erasure
      val constr = klass.getConstructors()(0)
      constr.newInstance().asInstanceOf[T]
    }
    
    abstract class A {
      def construct(implicit cm:ClassManifest[this.type]): this.type = {
        val res = newInstance[this.type]
        res
      }
    }
    
    class B extends A
    

    【讨论】:

    • 可以,但这几乎不是在 Scala 中做这种事情的最佳方式。
    • 为什么在这里使用 Manifests?没有擦除,没有参数化类型。
    【解决方案4】:

    看起来这是不可能的。根据 Scala 书(由 Oderski、Spoon、Venners 撰写)您不能创建抽象类型的实例。请参阅:抽象类型章节,货币案例研究。这可能会在稍后通过“虚拟类”得到支持。

    【讨论】:

      【解决方案5】:

      我提出以下模式:

      abstract class A($params) {
        // do common initialisation here
        def construct() : A
      
        def foo(...) = {
          ...
          val bar = construct()
          ...
        }
      }
      
      class B($moreparams) extends A($someparams) {
        // do special initialisation here
        def construct() = new B()
      }
      

      您现在拥有的所有冗余恰好是每个子类一行。我认为这是一个很小的代价,可以为 a) 一个可行的解决方案 b) 不使用反射(这基本上破坏了静态类型系统为您提供的所有保证)。

      我仍然很好奇为什么您需要在 A 中使用 construct。闻起来很腥。

      【讨论】:

        【解决方案6】:

        Monkey 回复我的评论后。解决此问题的一种方法是将Curiously Recurring Template Pattern (CRTP) 与 self 类型一起使用:

        abstract class A[T <: A[T]] { this: T =>
        
          def newInstance(): T;
        
          def construct(): T = {
            val res = newInstance()
            res
          }
        
          def some(): T = this
        }
        
        class B extends A[B] {
          def newInstance() = new B()
        }
        

        也许有更好的解决方案,但这是我目前发现的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-04-02
          • 1970-01-01
          • 2014-09-13
          • 1970-01-01
          • 2020-09-14
          • 1970-01-01
          相关资源
          最近更新 更多