Quiero crear un sistema de enity con algunas propiedades especiales, basadas en los rasgos de Scala.Conservación de las individualidades de rasgos al mezclarlas en
La idea principal es la siguiente: todos los componentes son rasgos que heredan de la rasgo común:
trait Component
trait ComponentA extends Component
veces, en el caso de una jerarquía más compleja y componentes interdependientes que puede conseguir como esto:
trait ComponentN extends ComponentM {
self: ComponentX with ComponentY =>
var a = 1
var b = "hello"
}
y así sucesivamente. He llegado a la conclusión de que los datos relevantes para cada componente deben estar contenidos en sí mismos y no en algún almacenamiento dentro de un Entity
o en otro lugar debido a la velocidad del acceso. Como nota al margen, esa es también la razón por la que todo es mutable, por lo que no hay necesidad de pensar en la inmutabilidad.
Entonces Entities
se crean, mezcla de los rasgos:
class Entity
class EntityANXY extends ComponentA
with ComponentN
with ComponentX
with ComponentY
Aquí todo está bien, sin embargo tengo un requisito especial que no sé cómo cumplir con el código. El requisito es la siguiente:
cada rasgo debe proporcionar un método de codificación que facilita la recogida de los datos relacionado con el rasgo en una forma universal, por ejemplo, en una forma de un JSON o una Map
como Map("a" -> "1", "b" -> "hello")
y un método de decodificación (?) para traducir dicho mapa, si se recibe, de nuevo en las variables relacionadas con los rasgos. Además: 1) todos los métodos de codificación y descodificación de todos los rasgos mixtos se llaman en un grupo, en un orden arbitrario por los métodos encode
y decode(Map)
y 2) deben estar disponibles para ser llamados por separado especificando un rasgo escriba, o mejor, mediante un parámetro de cadena como decode("component-n", Map)
.
No es posible utilizar métodos con el mismo nombre, ya que se perderán debido a sombreado o anulación. Puedo pensar en una solución, donde todos los métodos se almacenan en un Map[String, Map[String, String] => Unit]
para decodificar y Map[String,() => Map[String, String]]
para codificar en cada entidad. Esto funcionaría, el nombre y la llamada estarían disponibles. Sin embargo, esto dará como resultado el almacenamiento de la misma información en cada entidad que es inaceptable.
También es posible almacenar estos mapas en un objeto complementario para que no esté duplicado en cualquier lugar y llamar al método encode
y decode
del objeto con un parámetro adicional que denota una instancia particular de la entidad.
El requisito puede parecer extraño, pero es necesario debido a la velocidad y modularidad requeridas. Todas estas soluciones son torpes y creo que hay una solución mejor e idiomática en Scala, o quizás me falta algún patrón arquitectónico importante aquí. Entonces, ¿hay un enfoque más simple y más idiomático que el que tiene el objeto compañero?
EDIT: Creo que la agregación en lugar de la herencia probablemente podría resolver estos problemas, pero a un costo de no poder llamar a los métodos directamente en una entidad.
ACTUALIZACIÓN: Explorando la forma bastante prometedora propuesta por Rex Kerr, me he topado con algo que obstaculiza.Aquí es el caso de prueba:
trait Component {
def encode: Map[String, String]
def decode(m: Map[String, String])
}
abstract class Entity extends Component // so as to enforce the two methods
trait ComponentA extends Component {
var a = 10
def encode: Map[String, String] = Map("a" -> a.toString)
def decode(m: Map[String, String]) {
println("ComponentA: decode " + m)
m.get("a").collect{case aa => a = aa.toInt}
}
}
trait ComponentB extends ComponentA {
var b = 100
override def encode: Map[String, String] = super.encode + ("b" -> b.toString)
override def decode (m: Map[String, String]) {
println("ComponentB: decoding " + m)
super.decode(m)
m.get("b").foreach{bb => b = bb.toInt}
}
}
trait ComponentC extends Component {
var c = "hey!"
def encode: Map[String, String] = Map("c" -> c)
def decode(m: Map[String, String]) {
println("ComponentC: decode " + m)
m.get("c").collect{case cc => c = cc}
}
}
trait ComponentD extends ComponentB with ComponentC {
var d = 11.6f
override def encode: Map[String, String] = super.encode + ("d" -> d.toString)
override def decode(m: Map[String, String]) {
println("ComponentD: decode " + m)
super.decode(m)
m.get("d").collect{case dd => d = dd.toFloat}
}
}
y finalmente
class EntityA extends ComponentA with ComponentB with ComponentC with ComponentD
modo que
object Main {
def main(args: Array[String]) {
val ea = new EntityA
val map = Map("a" -> "1", "b" -> "3", "c" -> "what?", "d" -> "11.24")
println("BEFORE: " + ea.encode)
ea.decode(map)
println("AFTER: " + ea.encode)
}
}
lo que da:
BEFORE: Map(c -> hey!, d -> 11.6)
ComponentD: decode Map(a -> 1, b -> 3, c -> what?, d -> 11.24)
ComponentC: decode Map(a -> 1, b -> 3, c -> what?, d -> 11.24)
AFTER: Map(c -> what?, d -> 11.24)
Los componentes A y B no están influenciados, siendo cortado por la resolución de herencia norte. Entonces este enfoque solo es aplicable en ciertos casos de jerarquía. En este caso, vemos que el ComponentD
ha sombreado todo lo demás. Cualquier comentario es bienvenido
ACTUALIZACIÓN 2: coloco el comentario que responde a este problema aquí, para una mejor referencia:.. "Scala linealiza todos los rasgos Debe haber un supertrait de todo lo que terminará la cadena en su caso, que significa que C
y A
todavía deben llamar al super
, y Component
debe ser el que termine la cadena con una operación nula ". - Rex Kerr
¿Por qué no utilizar algún tipo de solución genérica para implementar la codificación, en lugar de codificar manualmente todos los métodos de codificar a sí mismo? –
@RobinGreen puede mostrar un ejemplo de una solución de este tipo? Estoy casi seguro de que implica mucha reflexión o mucho más fundición adicional (aunque automática) que la hecha a mano. Hay una gran cantidad de buenas soluciones genéricas, pero todas cuestan velocidad. Además, no estoy seguro de si esto me ayudará con mi objetivo principal en esta tarea: crear una solución delgada para manejar la decodificación de codificación individual. Aunque si conoces algunos ejemplos interesantes, por favor comparte, tal vez los he echado de menos y me servirán? – noncom
¿No se encontrará con un montón de problemas al mezclar dos componentes diferentes que definen una var con el mismo nombre, p.'trait A extends Component {var name =" "}' y 'trait B extends Component {var name =" "}'? No entiendo por qué no usas el patrón de pastel más idiomático. De esta forma, los enfrentamientos de nombres serán más raros y se evitarán más fácilmente, ¿sí? –