Scala可以约束对象图,以便只显示与上下文相关的那些对象吗?

时间:2012-02-23 01:39:27

标签: scala abstraction dci typeclass

有没有办法使用Scala的类型系统来简明地指定完整对象图的上下文相关子图?

DCI认为你经常有一个相当复杂的对象图,但在任何一个用例中你通常只想使用一个子图。您的FooBarBat,但是当您处于用例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才能访问binBazInRole2需要访问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的类型系统似乎足以表达这样的事情,但我无法弄明白。

3 个答案:

答案 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提供barbat中的一个,具体取决于{{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的定义中,可以摆脱BatExtr1,但我觉得“更难”使用的是:

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成分及其在给定用例中的作用所需的方法。

如果它变得更复杂,您可能必须创建类似伪角色特征的东西,它定义了多个用例共有的方法。然后,用例角色的自我类型注释将指向此伪特征,而伪特征自我类型注释指向相应的数据类。