在Scala中使用以下构建器模式。为简化起见,我使用了SKAction.playSoundFileNamed(...)
的3个实例,A
只包含instance1
且与field1
或field2
没有关联。问题是代码中的每个地方都必须使用field3
,val 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)
}
}
异常并获得良好的类型不匹配异常?我试图参数化所提到的所有类和方法,并使用密封特征,但到目前为止没有成功。
答案 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)"