如何在Scala中进行类型安全过滤?

时间:2017-07-04 11:22:04

标签: scala

我在db中有不同类型的实体,我希望能够以某种条件安全地过滤它们。我使用Slick进行数据库访问,但我认为这并不重要。

例如,假设我有User和Post实体。用户有id和email字段,Post有id和title字段。

现在我使用ADT来表示过滤器。看起来像这样:

sealed trait Filter {

  def byId(id: Long): Filter = and(ById(id))

  def byEmail(email: String): Filter = and(ByEmail(email))

  def byTitle(title: String): Filter = and(ByTitle(title))

  def and(other: Filter): Filter = And(this, other)

}

object Filter {
  def apply(): Filter = NoFilter
}

case class ById(id: Long) extends Filter

case class ByEmail(email: String) extends Filter 

case class ByTitle(title: String) extends Filter

case class And(a: Filter, b: Filter) extends Filter

case object NoFilter extends Filter

在UserDao中解释它。 PostDao看起来一样:

trait Dao[A] {
  def find(filter: Filter, offset: Int, limit: Int):Seq[A]
}

object UserDao extends Dao[User]{

  override def find(filter: Filter, offset: Int, limit: Int): Seq[Any] = {
    filterTable(filter).drop(offset).take(limit).result
  }

  private def filterTable(table: Query[UserTable, User, Seq], filter: Filter): Query[UserTable, User, Seq] =
    filter match {
      case ById(id) => table.filter(_.id === id)
      case ByEmail(email) => table.filter(_.email === email)
      case And(a, b) => filterTable(filterTable(table, a), b)
      case NoFilter => table
      case other =>
        log.warn(s"Filter not supported $other")
        table
    }
}

由通用服务使用:

class Service[A](dao: Dao[A]) {
  def find(filter: Filter): Seq[A] = {
    // do some stuff
    dao.find(filter, offset = 0, limit = 100)
    // do some other stuff
  }
}

如您所见,但它不安全。虽然按标题过滤用户不会失败,但它没有意义,可能是导致错误的原因。我想也许我为不同的实体创建了不同的ADT集合,但是我将不得不复制非常相似的过滤器(例如id过滤器)。总而言之,我希望有过滤器:

  1. 类型安全。您无法为用户
  2. 创建过滤器ByTitle
  3. 您不需要复制常见的过滤条件,例如ByIdAndOrIn等。
  4. 我该怎么办?也许ADT不是我需要的?

1 个答案:

答案 0 :(得分:1)

如果你创建了一堆"标记"您的域对象的特征,然后您可以使用它可以过滤的数据类型参数化Filter类型:

  trait Filter[-T] 

  case class ById(id: Long) extends Filter[HasId]
  case class ByEmail(email: String) extends Filter[HasEmail]
  case class ByTitle(title: String) extends Filter[HasTitle]
  case class And[A, B](a: Filter[A], b: Filter[B]) extends Filter[A with B]
  case class Or[A, B](a: Filter[A], b: Filter[B]) extends Filter[A with B] 

现在,您可以声明filterTable来约束它将接受的过滤器类型:

def filterTable(table: Query[UserTable, User, Seq], filter: Filter[User]) = ...

所以,filterTable(myTable, ById(1) and ByEmail("foo"))会编译,但是 filterTable(myTable, ByTitle("foo"))不会。