scala – 在混合它们的同时保留特质个性
我想基于
Scala特性创建一个具有一些特殊属性的enity系统.
主要思想是:所有组件都是从共同特征继承的特征: trait Component trait ComponentA extends Component 有时,如果是更复杂的层次结构和相互依赖的组件,它可以像这样: trait ComponentN extends ComponentM { self: ComponentX with ComponentY => var a = 1 var b = "hello" } 等等.我得出的结论是,由于访问速度的原因,与每个组件相关的数据本身应该包含在内,而不是包含在实体内部或其他地方的某些存储中.作为旁注 – 这也是为什么一切都是可变的,所以没有必要考虑不变性. 然后创建实体,混合特征: class Entity class EntityANXY extends ComponentA with ComponentN with ComponentX with ComponentY 这里一切都很好,但我确实有一个特殊的要求,我不知道如何满足代码.要求是这样的: 每个特征必须提供一种编码方法(?),便于以通用形式收集特征相关数据,例如以JSON形式或像Map一样的地图(“a” – >“1”,“b “ – >”hello“)以及将这样的地图(如果接收到的话)转换回与特征相关的变量的解码方法.另外:1)所有混合特征的所有编码和解码方法都是一堆调用,按实体的方法以任意顺序编码和解码(Map)和2)应该可以通过指定a来单独调用特征类型,或更好,通过字符串参数,如decode(“component-n”,Map). 不可能使用具有相同名称的方法,因为它们会因阴影或覆盖而丢失.我可以想到一个解决方案,其中所有方法都存储在Map [String,Map [String,String] =>单位]用于解码和Map [String,()=> Map [String,String]]用于在每个实体中进行编码.这可以工作 – 名字和一堆电话肯定是可用的.但是,这将导致在每个实体中存储相同的信息,这是不可接受的. 还可以将这些映射存储在伴随对象中,以便它不会在任何地方重复,并使用表示实体的特定实例的额外参数来调用对象的编码和解码方法. 这个要求可能看起来很奇怪,但由于所需的速度和模块性,这是必要的.所有这些解决方案都很笨拙,我认为在Scala中有一个更好的惯用解决方案,或者我可能在这里缺少一些重要的架构模式.那么有没有比伴随对象更简单,更惯用的方法? 编辑:我认为聚合而不是继承可能解决这些问题,但代价是无法直接在实体上调用方法. 更新:探索Rex Kerr提出的非常有前途的方法,我偶然发现了一些阻碍的事情.以下是测试用例: 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} } } 最后 class EntityA extends ComponentA with ComponentB with ComponentC with ComponentD 以便 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) } } 这使: BEFORE: Map(c -> hey!,d -> 11.6) ComponentD: decode Map(a -> 1,b -> 3,c -> what?,d -> 11.24) ComponentC: decode Map(a -> 1,d -> 11.24) AFTER: Map(c -> what?,d -> 11.24) A和B组件不受影响,由继承分辨率切断.因此,此方法仅适用于某些层次结构案例.在这种情况下,我们看到ComponentD已经遮蔽了其他所有内容.欢迎提出任何意见. 更新2:我在这里放置了回答这个问题的评论,以便更好地参考:“Scala线性化所有特征.应该有一个超级的东西将终止链.在你的情况下,这意味着C和A仍然应该调用超级,而Component应该是用no-op来终止链的人.“ – 雷克斯克尔 解决方法
特拉维斯有一个基本正确的答案;不知道为什么他删除了它.但是,无论如何,只要您愿意让编码方法采用额外的参数,并且解码时您很乐意设置可变变量,而不是创建新对象,那么您可以毫不费力地做到这一点. (在运行时有效的复杂特征堆叠范围从难以实现.)
基本观察是,当您将特征链接在一起时,它定义了超类调用的层次结构.如果这些调用中的每一个都处理该特征中的数据,那么只要您能找到获取所有数据的方法,就可以设置.所以 trait T { def encodeMe(s: Seq[String]): Seq[String] = Seq() def encode = encodeMe(Seq()) } trait A extends T { override def encodeMe(s: Seq[String]) = super.encodeMe(s) :+ "A" } trait B extends T { override def encodeMe(s: Seq[String]) = super.encodeMe(s) :+ "B" } 它有用吗? scala> val a = new A with B a: java.lang.Object with A with B = $anon$1@41a92be6 scala> a.encode res8: Seq[String] = List(A,B) scala> val b = new B with A b: java.lang.Object with B with A = $anon$1@3774acff scala> b.encode res9: Seq[String] = List(B,A) 确实!它不仅有效,而且您可以免费获得订单. 现在我们需要一种基于此编码设置变量的方法.在这里,我们遵循相同的模式 – 我们采取一些输入,并用它上升超级链.如果堆叠了很多特征,则可能需要将文本预先解析为地图或过滤掉适用于当前特征的那些部分.如果没有,只需将所有内容传递给超级,然后自行设置. trait T { var t = 0 def decode(m: Map[String,Int]) { m.get("t").foreach{ ti => t = ti } } } trait C extends T { var c = 1 override def decode(m: Map[String,Int]) { super.decode(m); m.get("c").foreach{ ci => c = ci } } } trait D extends T { var d = 1 override def decode(m: Map[String,Int]) { super.decode(m); m.get("d").foreach{ di => d = di } } } 这也像人们希望的那样起作用: scala> val c = new C with D c: java.lang.Object with C with D = $anon$1@549f9afb scala> val d = new D with C d: java.lang.Object with D with C = $anon$1@548ea21d scala> c.decode(Map("c"->4,"d"->2,"t"->5)) scala> "%d %d %d".format(c.t,c.c,c.d) res1: String = 5 4 2 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |