【问题标题】:How can I extend an abstract class with an optional member in Scala?如何在 Scala 中使用可选成员扩展抽象类?
【发布时间】:2017-02-26 17:11:09
【问题描述】:

我有一个抽象基类 Foo,我希望它的构造函数有一个可选参数。如果没有提供,我就给它一个None 值。

源 Foo 不会有父母,所以我只想在没有父母列表的情况下构造它们(保留父列表的默认值)

派生的 Foo 可能提供了父母,所以我想模仿 Foo 基类的签名。

以下是我的尝试:

abstract class Foo(val id: String, var parentIds: Option[List[String]]=None) { }

case class SourceFoo(override val id: String)
  extends Foo(id, parentIds=None) { }

case class DerivedFoo(override val id: String, 
                      override var parentIds: Option[List[String]])
  extends Foo(id, parentIds) { }

我收到一个无法覆盖可变变量的编译器错误(在 DerivedFoo 构造函数中引用 parentIds

此列表可能会更改,因此我不想将其设为 val(这消除了我的编译器问题)。

这是一个非常基本的 OO 问题,所以它一定比我想象的要简单。如何以惯用的方式实现我想要的行为?

【问题讨论】:

    标签: scala oop abstract-class case-class


    【解决方案1】:

    我在阅读documentation 后设法解决了这个问题:

    案例类的构造函数参数被视为公共值,可以直接访问。

    由于我的基类是抽象的,我可以简单地使用默认的 val 构造来扩展它。

    我只需要在 DerivedFoo 构造函数中指定 parentIds 是一个 var。

    abstract class Foo(id: String, parentIds: Option[List[String]]=None) { }
    
    case class SourceFoo(id: String) extends Foo(id) { }
    
    case class DerivedFoo(id: String, var parentIds: Option[List[String]]=None) 
        extends Foo(id, parentIds) { }
    

    【讨论】:

      【解决方案2】:

      这是另一种可能更好的方法。明确承认类参数和类成员之间的区别。如果您喜欢遵循此代码块,也可以将它们设为私有成员。

      abstract class Foo(identifier: String, parentIdentifiers: Option[List[String]]) { 
        val id = identifier
        var parentIds = parentIdentifiers
      }
      
      case class SourceFoo(override val id: String) extends Foo(id, parentIdentifiers = None) { }
      
      case class DerivedFoo(identifier: String, parentIdentifiers: Option[List[String]]) extends Foo(identifier, parentIdentifiers) { }
      

      之后,您可以创建 DerivedFoo 并按照您的预期引用成员,并且您不会有两个名称不同的成员。

      REPL 输出:

      scala> DerivedFoo("1", Some(List("200","201","202")))
      res0: DerivedFoo = DerivedFoo(1,Some(List(200, 201, 202)))
      
      scala> res0.parentIds
      res1: Option[List[String]] = Some(List(200, 201, 202))
      
      scala> res0.parentIds = Some(List("800", "801", "802"))
      res0.parentIds: Option[List[String]] = Some(List(800, 801, 802))
      

      【讨论】:

        【解决方案3】:

        我认为您可以通过如下更改抽象类中的参数名称来实现目标。

        abstract class Foo(val id: String, var parentIdentifiers: Option[List[String]]) { 
          parentIdentifiers = None
        }
        
        case class SourceFoo(override val id: String)
          extends Foo(id, parentIdentifiers = None) { }
        
        case class DerivedFoo(override val id: String, 
                              var parentIds: Option[List[String]])
          extends Foo(id, parentIds) { }
        

        【讨论】:

        • 请注意,DerivedFoo 将拥有 both 成员,因此请确保访问正确的成员。也许你想给他们更多的描述性名称来表示这一点 - parentIds 用于抽象类构造函数,preliminaryParentIds 用于DerivedFoo 的构造函数
        • 这是唯一的解决方案? :(
        • 别等我再给你一个
        • @TzachZohar 无论如何,这两个成员不是问题的根源吗?当他试图覆盖 var 时,如果编译器允许他这样做,那么仍然会有两个变量。子类型中的那个将是相同的名称,并且会“隐藏”超类型变量。但无论如何,我刚刚发布的另一个答案可能是 The Right Way(TM)
        【解决方案4】:

        对于突变,您可以import scala.collection.mutable 并使用mutable.ListBuffer 而不是List。 我当然假设您不会将DerivedFoo 实例的parentIdsSome 更改为None。 这将允许您使用 vals 但仍然具有可变状态。

        但我不会说可变状态是惯用的 Scala。

        您通常使用不可变的valList,只要您想更改列表,只需复制对象即可。

          val fooA = SourceFoo("a")
          val fooB = DerivedFoo("b", "a" :: Nil)
          val fooB2 = fooB.copy(parentIds = fooB.parentIds :+ "x")
        

        所以为了更惯用,你可以做的最简单的是

        sealed abstract class Foo(val id: String, val parentIdsOpt: Option[List[String]])
        
        case class SourceFoo(override val id: String)
          extends Foo(id, None)
        
        case class DerivedFoo(override val id: String, val parentIds: List[String])
          extends Foo(id, Some(parentIds))
        

        这与你所拥有的非常接近。

        请注意DerivedFoo.parentIds 不再是Option,因为DerivedFoo 总是有父母,所以你不必处理Option。 (不过,您仍然需要处理空列表)

        还要注意特征上的sealed 关键字,这不是必需的,但如果您想匹配抽象类或特征的实例,建议您这样做。 (只有在您拥有所有子类时才能使用sealed,在您的示例中似乎就是这种情况)

        【讨论】:

          猜你喜欢
          • 2017-01-30
          • 1970-01-01
          • 2016-08-16
          • 1970-01-01
          • 2015-12-24
          • 2015-04-10
          • 1970-01-01
          • 1970-01-01
          • 2023-03-04
          相关资源
          最近更新 更多