2012-02-23 19 views
5

¿Existe alguna manera de utilizar el sistema de tipos de Scala para especificar concisamente el subgráfico relevante al contexto de un gráfico de objetos completo?¿Puede Scala restringir un gráfico de objetos para que solo estén visibles los objetos relevantes para el contexto?

DCI argumenta que a menudo tiene un gráfico de objetos bastante complejo, pero en cualquier caso de uso, a menudo solo quiere trabajar con un sub-gráfico. Tiene un Foo que tiene un Bar y un Bat, pero cuando está en el caso de uso 1, solo le importa el Bar y cuando está en el caso de uso 2, solo sobre el Bat.

Por ejemplo, digamos que usted tiene esta estructura, y el uso de los casos role1 requiere Foo->Bar->Baz->Bin y Role2 caso de uso requiere 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{} 

Es fácil ver cómo se puede restringir el acceso en una sola clase mediante el uso de rasgos:

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/ 

Pero usted tiene que repetir este patrón para cada objeto relevante para el uso- caso. (Por ejemplo, se necesita un BazInRole1 acceder a bin y una BazInRole2 acceder a biz)

Mi pregunta es si hay alguna manera de evitar la escritura de todos estos rasgos fácil de conseguir-mal, de espacio de nombres de hacinamiento. Por ejemplo, yo podía imaginar algo así como el código (que no compila):

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 

Parece que tipo de sistema de Scala es lo suficientemente expresivo para hacer algo como esto, pero no puedo entenderlo.

+0

Creo que se puede lograr algo como esto con las clases de tipo. Simplemente haga que la clase-tipo sea la vista en el gráfico del objeto y acceda y manipule su contenido solo a través de la clase-tipo. – ziggystar

Respuesta

0

Si he entendido bien su pregunta (que no estoy seguro de) que desea Foo para proporcionar uno de entre barbat o dependiendo del parámetro tipo de Foo.

Mi primer disparo sería:

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

modo que

scala> Foo.create(Role1) 
res1: Foo[Role1.type] with BarExt = [email protected] scala> Foo.create(Role1).bar 

scala> Foo.create(Role1).bar 
res2: Bar = [email protected] 

scala> Foo.create(Role1).bat 
<console>:12: error: value bat is not a member of Foo[Role1.type] with BarExt 
       Foo.create(Role1).bat 

y

scala> Foo.create(Role2).bat 
res3: Bat = [email protected] 

scala> Foo.create(Role2).bar 
<console>:12: error: value bar is not a member of Foo[Role2.type] with BatExt 
       Foo.create(Role2).bar 

Se podría deshacerse de BarExt y BatExt tirando de las declaraciones correspondientes a las definiciones de r1 y r2, sin embargo me parece que es "más duro" para trabajar con eso:

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

En la línea de fondo, todavía no estoy convencido de que esto es exactamente lo que ha estado pidiendo, o es?

+0

No creo que sea exactamente lo que estoy buscando. Piensa en Baz (o, peor, un gráfico de 20 clases con referencias cruzadas). Para garantizar que Baz.bin solo esté disponible en Role1, ¿no tengo que definir un rasgo (xExt) por objeto por rol y escribir una conversión fn por borde? Tal vez eso es lo mejor que se puede hacer, pero * esperaba * encontrar una forma de "conectar en cascada" el parámetro de tipo de Foo a Baz de forma concisa. ¿Tiene sentido? –

+0

Entonces, se supone que * todos los métodos * están contenidos en el gráfico original, pero cuando se "visualiza" el gráfico de un rol determinado, solo se expone un subconjunto de esos métodos. ¿Asumo que por "subgráfico" te refieres al mismo conjunto de nodos pero con diferentes roles dependientes del tipo? – fotNelton

+0

Sí, eso es exactamente lo que estoy esperando. –

0

En this artima article en DCI, el autor presenta una forma de obtener una arquitectura DCI en Scala, que se parece a lo que está buscando.

La idea básica es definir los métodos que son relevantes para su caso de uso en un rasgo, pero en lugar de su enfoque utiliza una anotación de auto-tipo para asegurarse de que es un objeto de una cierta clase base.

Para que sea un poco más accesible: tiene una clase de datos Data, que contiene los componentes elementales de sus objetos de datos. Cuando se quiere realizar un cierto uso de los casos, lo que le gusta a considerar un objeto Data en cierto rol Role se puede preparar el papel como éste:

trait Role { self : Data => 
    def methodForOnlyThisUseCase = {...} 
} 

Para la ejecución del caso de uso, a continuación, crear una objeto específico de esta función a través de:

val myUseCaseObject = new Data with Role 

de esta manera, el objeto myUseCaseObject se limita exactamente a sus Data constituyentes y los métodos necesarios para su papel en el caso de uso dado.

Si se vuelve más compleja, puede que tenga que crear algo así como un rasgo seudo papel, que define los métodos comunes a varios casos de uso. La anotación de auto-tipo de la función de caso de uso volvería a este pseudo-rasgo, mientras que la anotación de auto-tipo de pseudo-rasgos apunta a la clase de datos correspondiente.

+0

Sí, es un artículo importante sobre DCI, y mi pregunta comienza con este tipo de diseño, pero el problema es que debe escribir explícitamente un rasgo para cada clase en función. En mi ejemplo, al final tener para crear un rasgo FooInRole1, FooInRole2 y BazInRole1, BazInRole2, e incluso se acaban de tener que crear una BarInRole1 con el fin de "atravesar" el tipo de rol de Baz. Es * posible * que esto sea tan bueno como usted puede hacer, pero parece que debería haber una manera de evitar tanto desorden en el espacio de nombres. –

1

No es muy concisa, y los miembros están ahí, simplemente imposible de usar, pero tal vez van en esta dirección sería aceptable?

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 
} 
Cuestiones relacionadas