如何在 Scala 中使用可选成员扩展抽象 class?
How can I extend an abstract class with an optional member in Scala?
我有一个抽象基类 class,Foo,我希望它的构造函数有一个可选参数。如果提供 none,我将只给它一个 None
值。
源 Foo 不会有 parents,所以我只想在没有 parents 列表的情况下构造它们(保留 parent 列表的默认值)
派生的 Foo 可能已经提供了 parents,所以我想模仿 Foo 基础的签名 class。
以下是我的尝试:
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 问题,所以它一定比我想象的要简单。我怎样才能以惯用的方式实现我想要的行为?
我认为您可以通过如下更改摘要中的参数名称来实现您的目标class。
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) { }
我在阅读 documentation 后设法解决了这个问题:
The constructor parameters of case classes are treated as public values and can be accessed directly.
因为我的基础 class 是抽象的,我可以简单地使用默认的 val
构造来扩展它。
我只需要指定 parentIds
是 DerivedFoo 构造函数中的一个变量。
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) { }
这是另一种可能更好的方法。明确承认 class 参数和 class 成员之间的区别。如果您喜欢遵循此代码块,也可以将它们设为私有成员。
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))
对于变异,你可以import scala.collection.mutable
并使用mutable.ListBuffer
而不是List
。
当然,我假设您不会将 DerivedFoo
实例的 parentIds
从 Some
更改为 None
。
这将允许您使用 val
s 但仍然具有可变状态。
但我不会说可变状态是惯用的 Scala。
你通常使用不可变的 val
和 List
,只要你想改变列表就复制 object。
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
总是有 parents,所以您不必处理 Option
. (不过你仍然需要处理空列表)
还要注意 trait 上的 sealed
关键字,这不是必需的,但如果您想匹配抽象 class 或 trait 的实例,则建议使用。 (只有当您拥有所有 sub-classes 时,您才可以使用 sealed
,在您的示例中似乎就是这种情况)
我有一个抽象基类 class,Foo,我希望它的构造函数有一个可选参数。如果提供 none,我将只给它一个 None
值。
源 Foo 不会有 parents,所以我只想在没有 parents 列表的情况下构造它们(保留 parent 列表的默认值)
派生的 Foo 可能已经提供了 parents,所以我想模仿 Foo 基础的签名 class。
以下是我的尝试:
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 问题,所以它一定比我想象的要简单。我怎样才能以惯用的方式实现我想要的行为?
我认为您可以通过如下更改摘要中的参数名称来实现您的目标class。
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) { }
我在阅读 documentation 后设法解决了这个问题:
The constructor parameters of case classes are treated as public values and can be accessed directly.
因为我的基础 class 是抽象的,我可以简单地使用默认的 val
构造来扩展它。
我只需要指定 parentIds
是 DerivedFoo 构造函数中的一个变量。
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) { }
这是另一种可能更好的方法。明确承认 class 参数和 class 成员之间的区别。如果您喜欢遵循此代码块,也可以将它们设为私有成员。
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))
对于变异,你可以import scala.collection.mutable
并使用mutable.ListBuffer
而不是List
。
当然,我假设您不会将 DerivedFoo
实例的 parentIds
从 Some
更改为 None
。
这将允许您使用 val
s 但仍然具有可变状态。
但我不会说可变状态是惯用的 Scala。
你通常使用不可变的 val
和 List
,只要你想改变列表就复制 object。
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
总是有 parents,所以您不必处理 Option
. (不过你仍然需要处理空列表)
还要注意 trait 上的 sealed
关键字,这不是必需的,但如果您想匹配抽象 class 或 trait 的实例,则建议使用。 (只有当您拥有所有 sub-classes 时,您才可以使用 sealed
,在您的示例中似乎就是这种情况)