2

Schema.org是标记词汇表(用于网络),并根据属性(无方法)定义了许多类型。我目前正在尝试在 Scala 中将该模式的一部分建模为内部模型类,以便与面向文档的数据库 (MongoDB) 和 Web 框架结合使用。

从LocalBusiness的定义中可以看出,schema.org 使用多重继承还包括来自“Place”类型的属性。所以我的问题是:你将如何在 Scala 中为这样的模式建模?

到目前为止,我提出了两种解决方案。第一个使用常规类来建模单个继承树,并使用特征来混合这些附加属性。

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 {}

第二个版本尝试使用案例类。但是,由于不推荐使用案例类继承,因此我无法如此轻松地对主要层次结构进行建模。

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, ...)

但是当然,当需要“地点”时,不能使用 LocalBusiness,例如当我尝试在 Google 地图上呈现某些内容时。

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中一样, Organizationextends也是如此。ThingPlace

要实例化它们,您需要创建指定所有属性值的匿名内部类。

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 = "$$$"
  }

}

如果任何字段具有合理的默认值,您可以在 trait 中指定默认值。

我将没有值的字段保留为空字符串,但将可选字段设置为 type 可能更有意义Option[String],以更好地表明它们的值未设置。你喜欢用Option,所以我用Option

这种方法的缺点是编译器在您实例化其中一个特征的每个地方都会生成一个匿名内部类。这可能会给您带来大量.class文件。然而,更重要的是,这意味着相同特征的不同实例将具有不同的类型。

编辑:

关于如何使用它从数据库加载对象,这在很大程度上取决于您访问数据库的方式。如果您使用对象映射器,您将希望按照映射器期望的方式构建模型对象。如果这种技巧适用于您的对象映射器,我会感到惊讶。

如果您正在编写自己的数据访问层,那么您可以简单地使用DAO存储库模式进行数据访问,将构建匿名内部类的逻辑放在其中。

这只是构造这些对象的一种方法。这甚至不是最好的方法,但它证明了这一点。

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 回答