我想創建一個基於Scala特性的具有一些特殊屬性的Enity系統。在保留特質個性的同時將它們混合在一起
主要的想法是這樣的:所有部件都是從共同的特點繼承性狀:
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("a" -> "1", "b" -> "hello")
之類的形式和解碼方法將這樣的地圖(如果收到的話)翻譯回與特徵相關的變量。此外:1)所有混入特徵的所有編碼和解碼方法被稱爲一堆,以Entity
的方法encode
和decode(Map)
和2)以任意順序被調用,應該可以通過指定特徵來單獨調用鍵入或更好,通過像decode("component-n", Map)
這樣的字符串參數。
不可能使用名稱相同的方法,因爲它們會因陰影或覆蓋而丟失。我可以想出一個解決方案,其中所有的方法都存儲在Map[String, Map[String, String] => Unit]
中用於解碼,而Map[String,() => Map[String, String]]
用於編碼中的每一個實體。這將工作 - 名稱以及一堆電話肯定會提供。但是,這會導致在每個實體中存儲不可接受的相同信息。
也可以到這些地圖存儲在伴侶對象,使其不被任何複製和調用該對象的encode
和decode
方法有一個額外的參數表示的實體的特定實例。
該要求可能看起來很奇怪,但由於所需的速度和模塊性,這是必要的。所有這些解決方案都很笨拙,我認爲Scala中有更好的和慣用的解決方案,或者我錯過了一些重要的架構模式。那麼是否有比伴隨對象更簡單和更習慣的方法?
編輯:我認爲聚合而不是繼承可能可以解決這些問題,但代價是無法直接在實體上調用方法。
更新:探索雷克斯克爾提出的相當有希望的方式,我偶然發現了一些阻礙。下面是測試例:
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的線性化所有的特質應該是一切都將終止鏈的supertrait在你的情況,這意味着C
和A
仍然應該叫super
,並且Component
應該是一個終止與無操作鏈。「 - 雷克斯克爾
你爲什麼不使用某種通用的解決方案,以實現自己的編碼,而不是手工編寫所有的編碼方法? –
@RobinGreen你能舉一個這樣的解決方案的例子嗎?我幾乎可以肯定的是,它涉及到比手工製作更多的反射或更多的額外投射(儘管是自動的)。有很多很好的通用解決方案,但它們都花費在速度上。此外,我不確定這是否能幫助我實現此任務的主要目標 - 爲處理單個編碼解碼創建一個超薄解決方案。雖然如果你知道任何有趣的例子,請分享,也許我錯過了他們,他們會適合? – noncom
混合兩個不同的組件時,不會遇到一系列問題,這兩個組件都定義了一個具有相同名稱的變量,例如'特質A擴展組件{var name =「」}'和'特質B擴展組件{var name =「」}'?我不明白你爲什麼不使用更習慣的蛋糕模式。這種名稱衝突會更罕見,更容易避免是嗎? –