Scala最佳实践:特质继承 vs 枚举。

11

我目前正在尝试使用Scala,并寻找最佳实践。我发现自己对解决单个问题有两种相反的方法。我想知道哪一个更好,为什么更传统,如果您知道其他更好的方法。第二种方法对我来说看起来更美观。

1. 基于枚举的解决方案

import org.squeryl.internals.DatabaseAdapter
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter}
import java.sql.Driver

object DBType extends Enumeration {
  val MySql, PostgreSql, H2 = Value

  def fromUrl(url: String) = {
    url match {
      case u if u.startsWith("jdbc:mysql:") => Some(MySql)
      case u if u.startsWith("jdbc:postgresql:") => Some(PostgreSql)
      case u if u.startsWith("jdbc:h2:") => Some(H2)
      case _ => None
    }
  }
}

case class DBType(typ: DBType) {
  lazy val driver: Driver = {
    val name = typ match {
      case DBType.MySql => "com.mysql.jdbc.Driver"
      case DBType.PostgreSql => "org.postgresql.Driver"
      case DBType.H2 => "org.h2.Driver"
    }
    Class.forName(name).newInstance().asInstanceOf[Driver]
  }
  lazy val adapter: DatabaseAdapter = {
    typ match {
      case DBType.MySql => new MySQLAdapter
      case DBType.PostgreSql => new PostgreSqlAdapter
      case DBType.H2 => new H2Adapter
    }
  }
}

2. 基于单例模式的解决方案

import org.squeryl.internals.DatabaseAdapter
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter}
import java.sql.Driver

trait DBType {
  def driver: Driver
  def adapter: DatabaseAdapter
}

object DBType {
  object MySql extends DBType {
    lazy val driver = Class.forName("com.mysql.jdbc.Driver").newInstance().asInstanceOf[Driver]
    lazy val adapter = new MySQLAdapter
  }

  object PostgreSql extends DBType {
    lazy val driver = Class.forName("org.postgresql.Driver").newInstance().asInstanceOf[Driver]
    lazy val adapter = new PostgreSqlAdapter
  }

  object H2 extends DBType {
    lazy val driver = Class.forName("org.h2.Driver").newInstance().asInstanceOf[Driver]
    lazy val adapter = new H2Adapter
  }

  def fromUrl(url: String) = {
    url match {
      case u if u.startsWith("jdbc:mysql:") => Some(MySql)
      case u if u.startsWith("jdbc:postgresql:") => Some(PostgreSql)
      case u if u.startsWith("jdbc:h2:") => Some(H2)
      case _ => None
    }
  }
}
3个回答

12
如果您声明一个密封特质DBType,您可以进行模式匹配并进行完整性检查(即,Scala会告诉您是否忘记了某个情况)。
无论如何,我不喜欢Scala的Enumeration,而我并不孤单。 我从不使用它,并且如果有什么需要枚举真正是最清晰的解决方案,则最好只需在Java中编写,使用Java的枚举。

我完全同意。Scala枚举是绝对没有用的。它们只提供自动生成连续值,我怀疑有人需要这个功能吗?相反,没有好的方法通过字符串ID查找值(在底层使用了反射),也没有合法的方法从Enumeration#Value解析枚举。 - Jiří Vypědřík

10

对于这个特定案例,您实际上不需要为每种数据库类型创建类;它只是数据。除非真正的情况极其复杂,否则我会使用基于映射和字符串解析的解决方案来最小化代码重复量:

case class DBRecord(url: String, driver: String, adapter: () => DatabaseAdapter) {}

class DBType(record: DBRecord) {
  lazy val driver = Class.forName(record.driver).newInstance().asInstanceOf[Driver]
  lazy val adapter = record.adapter()
}

object DBType {
  val knownDB = List(
    DBRecord("mysql", "com.mysql.jdbc.Driver", () => new MySQLAdapter),
    DBRecord("postgresql", "org.postgresql.Driver", () => new PostgreSqlAdapter),
    DBRecord("h2", "org.h2.Driver", () => new H2Adapter)
  )

  val urlLookup = knownDB.map(rec => rec.url -> rec).toMap

  def fromURL(url: String) = {
    val parts = url.split(':')
    if (parts.length < 3 || parts(0) != "jdbc") None
    else urlLookup.get(parts(1)).map(rec => new DBType(rec))
  }
}

4

我会选择单例模式,因为它可以更清晰地进行子类化。

另外,由于一些查询/子查询/运算符可能不同,您可能需要执行特定于数据库的操作和覆盖。

但是我建议尝试这样做:

import org.squeryl.internals.DatabaseAdapter
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter}
import java.sql.Driver

abstract class DBType(jdbcDriver: String) {
  lazy val driver = Class.forName(jdbcDriver).newInstance().asInstanceOf[Driver]
  def adapter: DatabaseAdapter
}


object DBType {
  object MySql extends DBType("com.mysql.jdbc.Driver") {
    lazy val adapter = new MySQLAdapter
  }

  object PostgreSql extends DBType("org.postgresql.Driver") {
    lazy val adapter = new PostgreSqlAdapter
  }

  object H2 extends DBType("org.h2.Driver") {
    lazy val adapter = new H2Adapter
  }

  def fromUrl(url: String) = {
    url match {
      case _ if url.startsWith("jdbc:mysql:") => Some(MySql(url))
      case _ if url.startsWith("jdbc:postgresql:") => Some(PostgreSql(url))
      case _ if url.startsWith("jdbc:h2:") => Some(H2(url))
      case _ => None
    }

}

如果这篇文章对您有帮助,请考虑点赞 :)

特质(Traits)不能有参数。这应该是一个抽象类吗? - Rex Kerr
是的,抱歉,我只是复制了你的代码,忘记了那个 trait。 - Franz Bettag
抱歉!这里是早上6点,我还没有睡觉 ;) 只是想帮忙。 - Franz Bettag
3
我认为这项服务并不是为了乞求分数。要有点尊严,朋友。我需要在这里标记什么为已回答?问题是“哪一个更好、为什么更传统,并且如果你可能知道其他更好的方法”,而复制粘贴我的代码然后进行基本的修正与此有何关系? - Nikita Volkov
你想要一个带有原因的意见,我给了你一个通过小的代码改进两者兼具。很抱歉,不会再发生了。此外,如果人们不经常忘记标记他们的问题已经回答了,那么其他人可能能够正确地使用未回答的选项卡。 - Franz Bettag

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接