0

在 Scala 中具有以下构建器模式。为了简化它,我使用了 3 个A这样的实例,它们instance1只包含or 并且与orfield1没有任何联系。问题是在我必须使用的代码中的任何地方,调用都不是潜在安全的。例如会在. 为了保护它,我必须将 case 与选项匹配并处理 None 情况:field2field3val s = A.instance1.field1.get; doSomething(s)getA.instance1.field2.getNone.get

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)
  }
}

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

如何在我的示例中使用幻像类型来避免出现运行时None异常并只获得不错的类型不匹配异常?我正在尝试参数化所有提到的类和方法并使用密封特征,但到目前为止没有成功。

4

2 回答 2

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 = ... }
于 2016-02-23T15:34:10.713 回答
0

我不确定这是否是您想要的,但我认为您可以将其作为基础。

首先是A类:

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

案例类具有空字符串的默认值。这允许我们创建具有分配任何字段值的任何 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)"
于 2016-02-23T17:00:37.080 回答