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
のメソッドによって任意の順序でまとめて呼び出さencode
れ、 decode(Map)
2) 特性タイプを指定することで個別に呼び出せるようにする必要があります。のような文字列パラメーターによってより良いdecode("component-n", Map)
。
シャドウイングまたはオーバーライドにより失われるため、同じ名前のメソッドを使用することはできません。すべてのメソッドがすべてのエンティティMap[String, Map[String, String] => Unit]
のデコードMap[String, () => Map[String, String]]
用およびエンコード用に格納されているソリューションを考えることができます。これは機能します - 名前による呼び出しと束呼び出しが確実に利用可能になります。ただし、これは受け入れられないすべてのエンティティに同じ情報を格納することになります。
これらのマップをコンパニオン オブジェクトに格納してどこにも複製しないようにし、エンティティの特定のインスタンスを示す追加のパラメーターを指定してオブジェクトのencode
andメソッドを呼び出すこともできます。decode
この要件は奇妙に思えるかもしれませんが、必要な速度とモジュール性のために必要です。これらの解決策はすべて不器用であり、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:より良い参照のために、この問題に答えるコメントをここに配置C
します。操作なしでチェーンを終了する必要があります。」–レックス・カーA
super
Component