2

Schema.orgは (Web 用の) マークアップ ボキャブラリであり、プロパティ (メソッドではありません) に関して多くの型を定義します。現在、ドキュメント指向データベース (MongoDB) および Web フレームワークと組み合わせて使用​​する内部モデル クラスとして、そのスキーマの一部を Scala でモデル化しようとしています。

LocalBusinessの定義に見られるように、schema.org は複数の継承を使用して、"Place" タイプのプロパティも含めます。私の質問は、Scala でそのようなスキーマをどのようにモデル化しますか?

私はこれまでに2つの解決策を考え出しました。最初のものは、通常のクラスを使用して単一の継承ツリーをモデル化し、特性を使用してそれらの追加のプロパティをミックスインします。

trait ThingA {
  var name: String = ""
  var url: String = ""
}

trait OrganizationA {
  var email: String = ""
}

trait PlaceA {
  var x: String = ""
  var y: String = ""
}

trait LocalBusinessA {
  var priceRange: String = ""
}

class OrganizationClassA extends ThingA with OrganizationA {}

class LocalBusinessClassA extends OrganizationClassA with PlaceA with LocalBusinessA {}

2 番目のバージョンでは、ケース クラスを使用しようとします。ただし、ケース クラスの継承は推奨されていないため、主要な階層を簡単にモデル化することはできません。

trait ThingB {
  val name: String
}

trait OrganizationB {
  val email: String
}

trait PlaceB {
  val x: String
  val y: String
}

trait LocalBusinessB {
  val priceRange: String
}

case class OrganizationClassB(val name: String, val email: String) extends ThingB with OrganizationB

case class LocalBusinessClassB(val name: String, val email: String, val x: String, val y: String, val priceRange: String) extends ThingB with OrganizationB with PlaceB with LocalBusinessB

これをモデル化するより良い方法はありますか?次のような構成を使用できます

case class LocalBusinessClassC(val thing:ThingClass, val place: PlaceClass, ...)

もちろん、「場所」が必要な場合、たとえば Google マップで何かをレンダリングしようとする場合など、LocalBusiness は使用できません。

4

1 に答える 1

1

どの方法が最適かは、オブジェクトを基になるデータストアにどのようにマッピングするかによって大きく異なります。

多重継承の必要性を考えると、考慮に値する可能性のあるアプローチは、単に特性を使用することです。これにより、コードの重複やボイラープレートを最小限に抑えて多重継承が可能になります。

trait Thing {
  val name: String               // required
  val url: Option[String] = None // reasonable default
}

trait Organization extends Thing {
  val email: Option[String] = None
}

trait Place extends Thing {
  val x: String
  val y: String
}

trait LocalBusiness extends Organization with Place {
  val priceRange: String
}

schema.orgと同様にOrganizationextendsThingに注意してください。Place

それらをインスタンス化するには、すべての属性の値を指定する匿名内部クラスを作成します。

object UseIt extends App {
  val home = new Place {
    val name = "Home"
    val x = "-86.586104"
    val y = "34.730369"
  }

  val oz = new Place {
    val name = "Oz"
    val x = "151.206890"
    val y = "-33.873651"
  }

  val paulis = new LocalBusiness {
    val name = "Pauli's"
    override val url = "http://www.paulisbarandgrill.com/"
    val x = "-86.713660"
    val y = "34.755092"
    val priceRange = "$$$"
  }

}

いずれかのフィールドに適切なデフォルト値がある場合は、特性でデフォルト値を指定できます。

値のないフィールドを空の文字列として残しましたが、値が設定されていないことをより適切に示すために、 type のオプション フィールドを作成する方がおそらく理にかなっていますOption[String]あなたは を使うOptionのが好きだったので、私は を使ってOptionいます。

このアプローチの欠点は、トレイトの 1 つをインスタンス化するたびに、コンパイラが匿名の内部クラスを生成することです。これにより、ファイルが爆発的に増加する可能性があり.classます。ただし、さらに重要なことは、同じ特性の異なるインスタンスが異なるタイプを持つことを意味します。

編集:

これを使用してデータベースからオブジェクトをロードする方法に関しては、データベースへのアクセス方法に大きく依存します。オブジェクト マッパーを使用する場合は、モデル オブジェクトを、マッパーが期待する構造に合わせて構造化する必要があります。この種のトリックがオブジェクト マッパーで機能する場合、私は驚かれることでしょう。

独自のデータ アクセス レイヤーを作成している場合は、データ アクセスにDAOまたはリポジトリパターンを使用するだけで、そこに匿名の内部クラスを構築するロジックを配置できます。

これは、これらのオブジェクトを構造化する 1 つの方法にすぎません。これは最善の方法ではありませんが、要点を示しています。

trait Database {
  // treats objects as simple key/value pairs
  def findObject(id: String): Option[Map[String, String]]
}

class ThingRepo(db: Database) {
  def findThing(id: String): Option[Thing] = {
    // Note that in this way, malformed objects (i.e. missing name) simply
    // return None. Logging or other responses for malformed objects is left
    // as an exercise :-)
    for {
      fields <- db.findObject(id) // load object from database
      name <- field.get("name")   // extract required field
    } yield {
      new Thing {
        val name = name
        val url = field.get("url")
      }
    }
  }
}

それだけではありません (オブジェクトを識別する方法、データベースに格納する方法、リポジトリを接続する方法、ポリモーフィック クエリを処理する方法など)。しかし、これは良いスタートになるはずです。

于 2012-05-29T22:12:42.407 に答える