【问题标题】:How to design abstract classes if methods don't have the exact same signature?如果方法没有完全相同的签名,如何设计抽象类?
【发布时间】:2018-03-04 11:13:05
【问题描述】:

这是一个“现实生活”的面向对象设计问题。我正在使用 Scala,并且对特定的 Scala 解决方案感兴趣,但我绝对愿意听取一般的想法。

我正在实施一个分支定界组合优化程序。该算法本身很容易实现。对于每个不同的问题,我们只需要实现一个类,该类包含有关搜索允许的邻居状态是什么、如何计算成本以及可能是什么下限等信息...

我还希望能够尝试不同的数据结构。例如,存储逻辑公式的一种方法是使用简单的整数列表。这表示一组子句,每个整数都是一个文字。但是,如果我们执行“双字面观察列表”之类的操作,并且通常存储一些有关公式的额外信息,我们可以获得更好的性能。

这一切都意味着这样的事情

object BnBSolver[S<:BnBState]{
  def solve(states: Seq[S], best_state:Option[S]): Option[S] = if (states.isEmpty) best_state else
     val next_state = states.head
     /* compare to best state, etc... */
     val new_states = new_branches ++ states.tail
     solve(new_states, new_best_state)
}

class BnBState[F<:Formula](clauses:F, assigned_variables) {
  def cost: Int

  def branches: Seq[BnBState] = {
    val ll = clauses.pick_variable
    List(
      BnBState(clauses.assign(ll), ll :: assigned_variables), 
      BnBState(clauses.assign(-ll), -ll :: assigned_variables)
    )
  }
}

case class Formula[F<:Formula[F]](clauses:List[List[Int]]) {
    def assign(ll: Int) :F = 
       Formula(clauses.filterNot(_ contains ll)
                      .map(_.filterNot(_==-ll))))

}

希望这不是太疯狂、错误或令人困惑。这里的整个问题是,公式中的这个assign 方法通常只采用将要分配的当前文字。但是,对于两个字面量的观察列表,您正在做一些懒惰的事情,需要您稍后知道之前分配了哪些字面量。

解决此问题的一种方法是,您只需将先前分配的文字列表保留在数据结构中,也许作为私人事物。使其成为一个自立的惰性数据结构。但是这个先前分配的列表实际上是任何使用公式类的人都可以自然获得的东西。因此,如果有必要,让使用它的人每次assign 时只提供列表是有意义的。

这里的问题是我们现在不能有一个抽象的Formula 类,它只声明一个assign(ll:Int):Formula。正常情况下是可以的,但是如果这是一个两字面的监视列表公式,它实际上是一个assign(literal: Int, previous_assignments: Seq[Int])

从使用它的类的角度来看,这还不错。但是,我们如何编写可以采用所有这些不同版本的Formula 的通用代码呢?由于签名的剧烈变化,它不能简单地是一个抽象的方法。我们也许可以强制用户总是提供完整的赋值变量,但这也是一种谎言。怎么办?

如果我写下某种适配器方法,该方法知道从哪里获取以前的任务,那么监视列表类就会变成一种常规的assign(Int) 类......我在想也许implicit 我们可以做饭。

【问题讨论】:

  • 我发现你的问题有点令人困惑。我的感觉是,您将算法的许多问题与(再次)Scala 的太多问题混为一谈。
  • 公式是否接受类型参数?
  • Btw solve 不会编译。

标签: scala oop inheritance abstract-class


【解决方案1】:

我会尽量让我的回答有点笼统,因为我不相信我完全遵循你正在尝试做的事情。总之……

一般来说,首先想到的应该是接受一个通用的超类作为参数。显然,这不适用于 IntSeq[Int]

你可以只有两种方法;有一个呼叫另一个。例如,只需将 Int 包装成带有一个元素的 Seq[Int] 并将其传递给另一个方法。

您也可以将参数包装在一些自定义类中,例如

class Assignment {
  ...
}
def int2Assignment(n: Int): Assignment = ...
def seq2Assignment(s: Seq[Int]): Assignment = ...
case class Formula[F<:Formula[F]](clauses:List[List[Int]]) {
    def assign(ll: Assignment) :F = ...
}

当然,您可以选择将这些转换方法设为隐式,以便调用者只需导入它们,而不是显式调用它们。

最后,您可以使用类型类来做到这一点:

trait Assigner[A] {
  ...
}
implicit val intAssigner = new Assigner[Int] {
  ...
}
implicit val seqAssigner = new Assigner[Seq[Int]] {
  ...
}
case class Formula[F<:Formula[F]](clauses:List[List[Int]]) {
    def assign[A : Assigner](ll: A) :F = ...
}

您也可以在类级别设置该类型参数:

case class Formula[A:Assigner,F<:Formula[A,F]](clauses:List[List[Int]]) {
    def assign(ll: A) :F = ...
}

这些路径中的哪一个最好取决于偏好以及它如何与其余代码相匹配。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-29
    • 2011-07-10
    • 1970-01-01
    • 1970-01-01
    • 2020-03-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多