gpt4 book ai didi

scala - 保留特质个性,同时将它们混合

转载 作者:行者123 更新时间:2023-12-04 12:23:11 26 4
gpt4 key购买 nike

我想创建一个基于 Scala 特征的具有一些特殊属性的实体系统。

主要思想是:所有组件都是继承自共同特征的特征:

trait Component
trait ComponentA extends Component

有时,在更复杂的层次结构和相互依赖的组件的情况下,它可能会变成这样:
trait ComponentN extends ComponentM {
self: ComponentX with ComponentY =>

var a = 1
var b = "hello"
}

等等。我得出的结论是,与每个组件相关的数据应包含在其自身中,而不应包含在 Entity 内的某些存储中。或其他地方,因为访问的速度。作为旁注 - 这也是为什么一切都是可变的,所以没有必要考虑不变性。

然后 Entities被创建,混合了特征:
class Entity

class EntityANXY extends ComponentA
with ComponentN
with ComponentX
with ComponentY

这里一切都很好,但是我确实有一个特殊要求,我不知道如何用代码来满足。要求是这样的:

每个特征必须提供一种编码方法(?),以便以通用形式收集与特征相关的数据,例如 JSON 或 Map 的形式。喜欢 Map("a" -> "1", "b" -> "hello")以及将此类映射(如果收到)翻译回特征相关变量的解码方法。另外:1)所有混入特征的所有编码和解码方法都以 Entity的任意顺序调用。的方法 encodedecode(Map)和 2) 应该可以通过指定特征类型来单独调用,或者更好的是,通过字符串参数如 decode("component-n", Map) .

不能使用具有相同名称的方法,因为它们会由于遮蔽或覆盖而丢失。我可以想到一个解决方案,其中所有方法都存储在 Map[String, Map[String, String] => Unit] 中用于解码和 Map[String, () => Map[String, String]]在每个实体中进行编码。这会起作用 - 别名和一堆电话肯定会可用。但是,这将导致在每个实体中存储相同的信息,这是 Not Acceptable 。

也可以将这些映射存储在一个伴生对象中,这样它就不会在任何地方重复,并调用对象的 encodedecode带有一个额外参数的方法,表示实体的特定实例。

这个要求可能看起来很奇怪,但由于所需的速度和模块化,它是必要的。所有这些解决方案都很笨拙,我认为 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, b -> 3, c -> what?, d -> 11.24)
AFTER: Map(c -> what?, d -> 11.24)

A 和 B 组件不受影响,被继承解析截断。所以这种方法只适用于某些层次结构的情况。在这种情况下,我们看到 ComponentD已经掩盖了其他一切。欢迎任何意见。

更新 2:为了更好地引用,我将回答这个问题的评论放在这里,以便更好地引用:“Scala 线性化了所有的特征。应该有一个 super 特征将终止链。在你的情况下,这意味着 CA 应该仍然调用 super ,并且 Component 应该是用无操作终止链的那个。” ——雷克斯·克尔

最佳答案

特拉维斯的答案基本正确。不知道他为什么删除它。但是,无论如何,只要您愿意让您的编码方法采用一个额外的参数,并且当您解码时您很乐意设置可变变量而不是创建新对象,您就可以做到这一点而不会有太多的烦恼。 (在运行时有效的复杂特征堆叠范围从困难到不可能。)

基本观察是,当您将特征链接在一起时,它定义了父类(super class)调用的层次结构。如果这些调用中的每一个都处理该特征中的数据,那么只要您能找到一种方法来取回所有数据,您就会被设置。所以

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)

确实!它不仅有效,而且您可以免费获得订单。

现在我们需要一种基于这种编码设置变量的方法。在这里,我们遵循相同的模式——我们接受一些输入,然后顺着它进入 super 链。如果您有很多特征堆叠,您可能希望将文本预解析为 map 或过滤掉适用于当前特征的那些部分。如果没有,只需将所有内容都传递给 super ,然后让自己追求它。
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

关于scala - 保留特质个性,同时将它们混合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12071941/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com