我正在编写一个Scala应用程序,我想在其中构建来自文本数据的对象。基类是PageElement:
class PageElement {
def make(buf: String): Option[PageElement]
}
对象将是子类类型,例如
case class PeTypeA(val i: Int) extends PageElement {
def make(buf: String): Option[PageElement] = {
// simple example
if (buf.length > 10) {
Some(PeTypeA(11))
} else {
None
}
}
}
case class PeTypeB(val s: String) extends PageElement {
def make(buf: String): Option[PageElement] = {
// simple example
if (buf.length <= 10) {
Some(PeTypeB("foo"))
} else {
None
}
}
}
我想编写一个以字符串作为输入的函数doIt
,并依次调用每个子类上的make
函数。返回Some的第一个make
函数将是doIt
的输出。
我认为make
应该是每个类的静态函数,比如
object PeTypeA {
def make(buf: String): Option[PeTypeA] = ...
}
但是我不知道如何列出我将在doIt
内迭代的这些函数的列表。
答案 0 :(得分:2)
此解决方案类似于@ tsearcher,但有点不同。
我想说你应该做Scala标准集合所做的事情,即引入一个由伴随对象实现的新特征PageElementFactory
:
trait PageElementFactory[+T <: PageElement] {
def make(buf: String): Option[T]
}
case class PeTypeA(val i: Int) extends PageElement {
}
object PeTypeA extends PageElementFactory[PeTypeA] {
override def make(buf: String): Option[PeTypeA] = {
// simple example
if (buf.length > 10) {
Some(PeTypeA(11))
} else {
None
}
}
}
case class PeTypeB(val s: String) extends PageElement {
}
object PeTypeB extends PageElementFactory[PeTypeB] {
override def make(buf: String): Option[PeTypeB] = {
// simple example
if (buf.length <= 10) {
Some(PeTypeB("foo"))
} else {
None
}
}
}
然后您可以将其用作:
abstract class PageElement {
}
object PageElement {
private val allFactories: List[PageElementFactory[PageElement]] = List(PeTypeA, PeTypeB)
def doIt(buf: String): Option[PageElement] = {
allFactories.foldLeft[Option[PageElement]](None)((acc, fact) => acc.orElse(fact.make(buf)))
}
}
如果有许多不同的子类型且性能很重要,您可以将doIt
写为
def doIt(buf: String): Option[PageElement] = {
@tailrec
def impl(factories: List[PageElementFactory[PageElement]]): Option[PageElement] = factories match {
case f :: rest => f.make(buf) match {
case ope@Some(pe) => ope
case None => impl(rest)
}
case Nil => None
}
impl(allFactories)
}
如果成功,此版本将减少一些迭代次数。
PageElementFactory
作为通用的奖励是,如果你有一个复杂的节点嵌套结构,就像在HTML中一些节点只能有某些类型的孩子,你可以通过拥有类型来表达几个集合而不是单个allFactories
。就像你有PeTypeA
只有PeTypeA1
或PeTypeA2
类型的孩子的情况一样,你可能还有
abstract class PeTypeAChild extends PageElement {
}
class PeTypeA1 extends PeTypeAChild {
}
object PeTypeA1 extends PageElementFactory[PeTypeA1] {
override def make(buf: String): Option[PeTypeA1] = ???
}
class PeTypeA2 extends PeTypeAChild {
}
object PeTypeA2 extends PageElementFactory[PeTypeA2] {
override def make(buf: String): Option[PeTypeA2] = ???
}
然后
object PageElement {
def doItImpl[T <: PageElement](buf: String, factories: List[PageElementFactory[T]]): Option[T] = {
factories.foldLeft[Option[T]](None)((acc, fact) => acc.orElse(fact.make(buf)))
}
private val aChildFactories: List[PageElementFactory[PeTypeAChild]] = List(PeTypeA1, PeTypeA2)
def doItA(buf: String): Option[PeTypeAChild] = doItImpl(buf, aChildFactories)
private val allFactories: List[PageElementFactory[PageElement]] = List(PeTypeA, PeTypeA1, PeTypeA2, PeTypeB)
def doIt(buf: String): Option[PageElement] = doItImpl(buf, allFactories)
}
请注意doItA
如何返回更具体的Option[PeTypeAChild]
。
如果这种情况很复杂,那么树形结构不是您的情况,您可以使用非通用PageElementFactory
来简化代码:
trait PageElementFactory {
def make(buf: String): Option[PageElement]
}
答案 1 :(得分:0)
以下是一种可能的解决方案:
sealed trait PageElement
case class PeTypeA(i: Int) extends PageElement
case class PeTypeB(s: String) extends PageElement
object PeTypeA {
def make(buf: String): Option[PageElement] = {
// simple example
if (buf.length > 10) {
Some(PeTypeA(11))
} else {
None
}
}
}
object PeTypeB {
def make(buf: String): Option[PageElement] = {
// simple example
if (buf.length <= 10) {
Some(PeTypeB("foo"))
} else {
None
}
}
}
def doIt(buf: String): Option[PageElement] = {
PeTypeA.make(buf).orElse(PeTypeB.make(buf))
}
val a: Option[PageElement] = doIt("abcdefghijk")
val b: Option[PageElement] = doIt("abcdefghij")
a.foreach(println) // PeTypeA(11)
b.foreach(println) // PeTypeB(foo)