7

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]]用およびエンコード用に格納されているソリューションを考えることができます。これは機能します - 名前による呼び出しと束呼び出しが確実に利用可能になります。ただし、これは受け入れられないすべてのエンティティに同じ情報を格納することになります。

これらのマップをコンパニオン オブジェクトに格納してどこにも複製しないようにし、エンティティの特定のインスタンスを示す追加のパラメーターを指定してオブジェクトのencodeandメソッドを呼び出すこともできます。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します。操作なしでチェーンを終了する必要があります。」–レックス・カーAsuperComponent

4

1 に答える 1

5

トラビスは本質的に正しい答えを持っていました。なぜ彼がそれを削除したのかわかりません。しかし、いずれにせよ、エンコーディング メソッドに追加のパラメーターを使用させ、デコードするときに新しいオブジェクトを作成するのではなく、変更可能な変数を設定するだけでよければ、それほど苦労することなくこれを行うことができます。(実行時に効果的に複雑な特性スタッキングを行うことは、困難なものから不可能なものまであります。)

基本的な観察は、トレイトを連鎖させると、スーパークラス呼び出しの階層が定義されるということです。これらの呼び出しのそれぞれがその特性のデータを処理する場合、そのすべてのデータを取得する方法を見つけることができる限り、設定は完了です。そう

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
于 2012-08-22T18:18:08.537 に答える