有没有办法使用Scala的类型系统来简明地指定完整对象图的上下文相关子图?
DCI认为你经常有一个相当复杂的对象图,但在任何一个用例中你通常只想使用一个子图。您的Foo
有Bar
和Bat
,但是当您处于用例1时,您只关心Bar
以及何时使用 - 案例2,仅涉及Bat
。
例如,假设您有此结构,并且Role1用例需要Foo->Bar->Baz->Bin
,而Role2用例需要Foo->Bat->Baz->Buz
:
class Foo{
val bar = new Bar() //Only relevant to Role 1
val bat = new Bat() //Only relevant to Role 2
}
class Bar {
val baz = new Baz()
}
class Bat {
val baz = new Baz()
}
//Relevant to both Role 1 and 2 (via Bar or Bat)
class Baz {
val bin = new Bin() //Only relevant to Role 1
val buz = new Buz() //Only relevant to Role 2
}
class Bin{}
class Buz{}
通过使用特征很容易看出如何限制单个类中的访问权限:
trait FooInRole1 { def bar : Bar } //Define accessor in trait
s/Foo/Foo extends FooInRole1/ //Change Foo's declaration to implement trait
val f : FooInRole1 = new Foo //LHS is i'face, RHS is implementation
//f.bat <--Compile error Irrelevant field is not available. \o/
但是您必须为与用例相关的每个对象重复此模式。 (例如,您需要BazInRole1
才能访问bin
而BazInRole2
需要访问biz
)
我的问题是,是否有某种方法可以避免编写所有这些易于获取的错误,名称空间拥挤的特征。例如,我可以想象像这样的代码(不编译):
class Foo[T] {
T match {
case r1 : Role1 => def bar : Bar[T]
case r2 : Role2 => def bat : Bat[T]
case _ => //Nothing
}
}
val fInRole1 = new Foo[Role1] //Provides Foo->Bar->Baz->Bin
val fInRole2 = new Foo[Role2] //Provides Foo->Bat->Baz->Buz
Scala的类型系统似乎足以表达这样的事情,但我无法弄明白。
答案 0 :(得分:1)
不是非常简洁,成员在那里,只是不可能使用,但是可能会朝着这个方向前进吗?
class Foo[R] {
def bar(implicit ev: R <:< Role1) = new Bar[R] //Only relevant to Role 1
def bat(implicit ev: R <:< Role2) = new Bat[R] //Only relevant to Role 2
}
答案 1 :(得分:0)
如果我正确理解了您的问题(我不确定),您希望Foo
提供bar
或bat
中的一个,具体取决于{{1}的类型参数}}
我的第一枪是:
Foo
这样
class Bar
class Bat
trait BarExt { def bar = new Bar }
trait BatExt { def bat = new Bat }
trait Role
case object Role1 extends Role
case object Role2 extends Role
trait RoleProvider[From <: Role, To] {
def apply(): To
}
object RoleProvider {
implicit val r1 = new RoleProvider[Role1.type, Foo[Role1.type] with BarExt] {
def apply() = new Foo[Role1.type] with BarExt
}
implicit val r2 = new RoleProvider[Role2.type, Foo[Role2.type] with BatExt] {
def apply() = new Foo[Role2.type] with BatExt
}
}
class Foo[T <: Role]
object Foo {
def create[T <: Role, To](f: T)(implicit rp: RoleProvider[T,To]): To = rp()
}
和
scala> Foo.create(Role1)
res1: Foo[Role1.type] with BarExt = RoleProvider$$anon$3$$anon$1@187b2d93 scala> Foo.create(Role1).bar
scala> Foo.create(Role1).bar
res2: Bar = Bar@7ea4b9da
scala> Foo.create(Role1).bat
<console>:12: error: value bat is not a member of Foo[Role1.type] with BarExt
Foo.create(Role1).bat
通过将相应的声明提取到scala> Foo.create(Role2).bat
res3: Bat = Bat@56352b57
scala> Foo.create(Role2).bar
<console>:12: error: value bar is not a member of Foo[Role2.type] with BatExt
Foo.create(Role2).bar
和BarExt
的定义中,可以摆脱BatExt
和r1
,但我觉得“更难”使用的是:
r2
在底线,我仍然不相信这正是你一直要求的,或者是它?
答案 2 :(得分:0)
在DCI的this artima article中,作者提出了一种在Scala中获得DCI架构的方法,它看起来就像你的目标。
基本思想是在特征中定义与用例相关的方法,但不是使用自己的方法,而是使用自我类型注释来确保它是某个基类的对象。
所以为了使它更平易近人:你有一个数据类Data
,它包含数据对象的基本组件。当您想要实现某个特定用例时,可以考虑某个角色Data
中的Role
对象,您可以像这样准备角色:
trait Role { self : Data =>
def methodForOnlyThisUseCase = {...}
}
为了执行用例,您可以通过以下方式创建特定于此角色的对象:
val myUseCaseObject = new Data with Role
像这样,对象myUseCaseObject
仅限于其Data
成分及其在给定用例中的作用所需的方法。
如果它变得更复杂,您可能必须创建类似伪角色特征的东西,它定义了多个用例共有的方法。然后,用例角色的自我类型注释将指向此伪特征,而伪特征自我类型注释指向相应的数据类。