具有工厂功能的Scala用例

时间:2018-01-26 21:16:34

标签: scala

我正在编写一个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内迭代的这些函数的列表。

2 个答案:

答案 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只有PeTypeA1PeTypeA2类型的孩子的情况一样,你可能还有

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)