Scala Builder模式与幻影类型

时间:2016-02-23 14:09:11

标签: scala types scala-2.10 builder-pattern

在Scala中使用以下构建器模式。为简化起见,我使用了SKAction.playSoundFileNamed(...)的3个实例,A只包含instance1且与field1field2没有关联。问题是代码中的每个地方都必须使用field3val s = A.instance1.field1.get; doSomething(s)调用不可能安全。例如,get上的A.instance1.field2.get会失败。为了保护它,我必须将案例与选项相匹配并处理无案例:

None.get

另一种解决方案是使用参数化类型,也称为幻像类型。我发现很少有关于该主题的好教程,并且在其中任何一个中都找不到如何在Scala中使用幻像类型和实际数据(或状态)实现类型安全构建器模式 - 所有示例仅描述方法。

如何在我的示例中使用幻像类型以避免获得运行时object A { val instance1 = new ABuilder().withField1("abc").build1 val instance2 = new ABuilder().withField1("abc").withField2("def").build2 val instance3 = new ABuilder().withField1("abc").withField3("def").build1 } case class A(builder: ABuilder) { val field1: Option[String] = builder.field1 val field2: Option[String] = builder.field2 val field3: Option[String] = builder.field3 } class ABuilder { var field1: Option[String] = None var field2: Option[String] = None var field3: Option[String] = None def withField1(f: String): ABuilder = { this.field1 = Some(f) this } def withField2(f: String): ABuilder = { this.field2 = Some(f) this } def withField3(f: String): ABuilder = { this.field3 = Some(f) this } def build1: A = { require(field1.isDefined, "field 1 must not be None") A(this) } def build2: A = { require(field1.isDefined, "field 1 must not be None") require(field2.isDefined, "field 2 must not be None") A(this) } } 异常并获得良好的类型不匹配异常?我试图参数化所提到的所有类和方法,并使用密封特征,但到目前为止没有成功。

2 个答案:

答案 0 :(得分:1)

如果你真的想使用幻影类型,你可以做

object PhantomExample {
  sealed trait BaseA
  class BaseAWith1 extends BaseA
  final class BaseAWith12 extends BaseAWith1

  object A {
    val instance1 = new ABuilder().withField1("abc").build1
    val instance2 = new ABuilder().withField1("abc").withField2("def").build2
  }

  case class A[AType <: BaseA](builder: ABuilder) {
    def field1[T >: AType <: BaseAWith1] = builder.field1.get
    def field2[T >: AType <: BaseAWith12] = builder.field2.get
  }

  class ABuilder {
    var field1: Option[String] = None
    var field2: Option[String] = None
    def withField1(f: String): ABuilder = {
      this.field1 = Some(f)
      this
    }
    def withField2(f: String): ABuilder = {
      this.field2 = Some(f)
      this
    }
    def build1: A[BaseAWith1] = {
      require(field1.isDefined, "field 1 must not be None")
      A(this)
    }
    def build2: A[BaseAWith12] = {
      require(field1.isDefined, "field 1 must not be None")
      require(field2.isDefined, "field 2 must not be None")
      A(this)
    }
  }

  val x = A.instance1.field1                      //> x  : String = abc
  val x2 = A.instance2.field1                     //> x2  : String = abc
  val x3 = A.instance2.field2                     //> x3  : String = def

  // This gives compilation error
  //val x2 = A.instance1.field2
}

但是,我不建议在生产中使用这种代码。我觉得它看起来很难看,编译错误看起来很神秘,而恕我直言并不是最好的解决方案。想一想,如果你的实例如此不同,也许它们甚至不是同一个具体类的实例?

trait BaseA {
  def field1
}
class A1 extends BaseA { }
class A2 extends BaseA { ... def field2 = ... }

答案 1 :(得分:0)

我不确定这是不是你想要的,但我认为你可以把它作为基础。

首先是A类:

case class A(field1: String = "", 
              field2: String = "",
              field3: String = "")

case类的默认值为空字符串。这允许我们创建任何分配了任何字段值的A对象,而无需关注None值。

例如:

val b2 = A("abc", "def")
> b2: A = A(abc,def,)

val b1 = A("abc")
> b1: A = A(abc,,)

val notValidB = A(field2 = "xyz")
> notValidB: A = A(,xyz,)

正如您所见,b2和b1是有效对象,而notValidB无效,因为您的对象需要field1。

您可以创建另一个使用模式匹配来验证A对象的函数,然后继续执行确定的操作。

def determineAObj(obj: A): Unit = obj match {
  case A(f1, f2, _) if !f1.isEmpty && !f2.isEmpty => println("Is build2")
  case A(f1, _, _) if !f1.isEmpty => println("Is build1")
  case _ => println("This object doesn't match (build1 | build2)")
}

然后运行:

determineAObj(b1)
> "Is build1"

determineAObj(b2)
> "Is build2"

determineAObj(notValidB)
> "This object doesn't match (build1 | build2)"