Может ли Scala ограничить граф объектов, чтобы были видны только те объекты, которые имеют отношение к контексту?
Есть ли способ использовать систему типов 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 достаточно выразительна, чтобы делать что-то подобное, но я не могу понять это.
3 ответа
Не очень кратко, и члены там, просто невозможно использовать, но, возможно, движение в этом направлении будет приемлемым?
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
}
Если я правильно понял ваш вопрос (в чем я не уверен), вы хотите Foo
предоставить один из bar
или же bat
в зависимости от параметра типа 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
Однако мне труднее работать с этим:
implicit val r1 = new RoleProvider[Role1.type, Foo[Role1.type] { val bar: Bar }] {
def apply() = new Foo[Role1.type] { val bar = new Bar }
}
implicit val r2 = new RoleProvider[Role2.type, Foo[Role2.type] { val bat: Bat }] {
def apply() = new Foo[Role2.type] { val bat = new Bat }
}
В итоге я все еще не уверен, что это именно то, о чем вы просили, или это так?
В этой статье о DCI автор представляет способ получить архитектуру DCI в Scala, которая выглядит как то, к чему вы стремитесь.
Основная идея состоит в том, чтобы определить методы, относящиеся к вашему варианту использования в признаке, но вместо вашего подхода он использует аннотацию самостоятельного типа, чтобы гарантировать, что он является объектом определенного базового класса.
Итак, чтобы сделать это немного более доступным: у вас есть класс данных Data
, который содержит элементарные компоненты ваших объектов данных. Когда вы хотите реализовать определенный вариант использования, который любит рассматривать Data
объект в определенной роли Role
Вы можете подготовить роль следующим образом:
trait Role { self : Data =>
def methodForOnlyThisUseCase = {...}
}
Для выполнения варианта использования вы затем создаете объект, специфичный для этой роли, с помощью:
val myUseCaseObject = new Data with Role
Как это, объект myUseCaseObject
ограничивается именно его Data
составляющие и методы, необходимые для его роли в данном сценарии использования.
Если это становится более сложным, вам, возможно, придется создать что-то вроде псевдоролевой черты, которая определяет методы, общие для нескольких вариантов использования. Самостоятельная аннотация роли варианта использования будет затем указывать на эту псевдотракт, в то время как псевдотрактовая аннотация собственного типа указывает на соответствующий класс данных.