You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala中如何避免在Object类中重复编写类型边界[T <: HandPoints[T]]

简化Scala中重复的自类型边界写法

我正在编写卡牌计分相关的Scala代码,通过HandPoints特质实现不同的计分规则(比如常规积分直接转换为得分,双倍积分则翻倍后转换)。但所有卡牌检查函数都需要重复声明[T <: HandPoints[T]]这个类型边界,显得冗余。这些函数都定义在object中,没法给类添加类型参数,希望在保留静态方法的同时简化代码。

原代码实现

HandPoints相关定义

trait HandPoints[T] {
  def toHandScore: HandScore
  def zero: T
  def add(that: T): T
}

case class RegularPoint(points: Int) extends HandPoints[RegularPoint] {
  override def toHandScore: HandScore = HandScore(points)
  override def zero: RegularPoint = RegularPoint(0)
  override def add(that: RegularPoint): RegularPoint = RegularPoint(points + that.points)
}

case class DoublingPoints(points: Int) extends HandPoints[DoublingPoints] {
  override def toHandScore: HandScore = HandScore(points*2)
  override def zero: DoublingPoints = DoublingPoints(0)
  override def add(that: DoublingPoints): DoublingPoints = DoublingPoints(points + that.points)
}

case class HandScore(score: Int)

卡牌评估函数(存在重复类型边界问题)

trait Card {
  def getValue: Int
  def getSuit: String
}

def scored[T <: HandPoints[T]](score: T)(boolean: Boolean): T = {
  if (boolean) score else score.zero
}

def isAllEvens[T <: HandPoints[T]](score: T)(cards: List[Card]): T = {
  scored(score) {
    cards.forall(_.getValue % 2 == 0)
  }
}

def isAllReds[T <: HandPoints[T]](score: T)(cards: List[Card]): T = {
  scored(score) {
    cards.forall(c => List("HEARTS", "DIAMONDS").contains(c.getSuit))
  }
}

def isAllNoDuplicates[T <: HandPoints[T]](score: T)(cards: List[Card]): T = {
  scored(score) {
    cards.distinct == cards
  }
}

val regularGameCriteria: List[List[Card] => RegularPoint] = List(
  isAllEvens(RegularPoint(1)),
  isAllReds(RegularPoint(3)),
  isAllNoDuplicates(RegularPoint(5))
)

val beginnerGameCriteria: List[List[Card] => RegularPoint] = List(
  isAllEvens(RegularPoint(1)),
  isAllReds(RegularPoint(1)),
  isAllNoDuplicates(RegularPoint(1))
)

val superGameCriteria: List[List[Card] => DoublingPoints] = List(
  isAllEvens(DoublingPoints(1)),
  isAllReds(DoublingPoints(3)),
  isAllNoDuplicates(DoublingPoints(5))
)

def countScore[T <: HandPoints[T]](scoreList: List[List[Card] => T])(melds: List[Card]): T = {
  scoreList.map(f => f(melds)).reduce((a, b) => a.add(b))
}

def regularGameScore(cards: List[Card]): RegularPoint = {
  countScore(regularGameCriteria)(cards)
}

def beginnerGameScore(cards: List[Card]): RegularPoint = {
  countScore(beginnerGameCriteria)(cards)
}

def superGameScore(cards: List[Card]): DoublingPoints = {
  countScore(superGameCriteria)(cards)
}

优化方案

方案1:隐式上下文边界消除重复约束

在定义函数的object中创建一个隐式证据特质,封装重复的类型约束逻辑:

// 定义隐式证据类,封装T <: HandPoints[T]的约束
trait SelfHandPoint[T]
object SelfHandPoint {
  implicit def ev[T <: HandPoints[T]]: SelfHandPoint[T] = new SelfHandPoint[T] {}
}

之后所有函数的类型参数改用上下文边界[T: SelfHandPoint],替代重复的[T <: HandPoints[T]]:

def scored[T: SelfHandPoint](score: T)(boolean: Boolean): T = {
  if (boolean) score else score.zero
}

def isAllEvens[T: SelfHandPoint](score: T)(cards: List[Card]): T = {
  scored(score) {
    cards.forall(_.getValue % 2 == 0)
  }
}

def isAllReds[T: SelfHandPoint](score: T)(cards: List[Card]): T = {
  scored(score) {
    cards.forall(c => List("HEARTS", "DIAMONDS").contains(c.getSuit))
  }
}

def isAllNoDuplicates[T: SelfHandPoint](score: T)(cards: List[Card]): T = {
  scored(score) {
    cards.distinct == cards
  }
}

def countScore[T: SelfHandPoint](scoreList: List[List[Card] => T])(melds: List[Card]): T = {
  scoreList.map(f => f(melds)).reduce((a, b) => a.add(b))
}

这种方式完全消除了重复的类型边界声明,代码简洁性大幅提升。

方案2:重构HandPoints为自类型特质

修改HandPoints的定义,使用自类型替代泛型参数,子类无需显式指定泛型类型,函数的类型约束也会简化:

trait HandPoints {
  self: HandPoints =>
  def toHandScore: HandScore
  def zero: this.type
  def add(that: this.type): this.type
}

case class RegularPoint(points: Int) extends HandPoints {
  override def toHandScore: HandScore = HandScore(points)
  override def zero: RegularPoint = RegularPoint(0)
  override def add(that: RegularPoint): RegularPoint = RegularPoint(points + that.points)
}

case class DoublingPoints(points: Int) extends HandPoints {
  override def toHandScore: HandScore = HandScore(points * 2)
  override def zero: DoublingPoints = DoublingPoints(0)
  override def add(that: DoublingPoints): DoublingPoints = DoublingPoints(points + that.points)
}

此时函数的类型约束简化为[T <: HandPoints],仅需在scored方法中做一次类型转换:

def scored[T <: HandPoints](score: T)(boolean: Boolean): T = {
  if (boolean) score else score.zero.asInstanceOf[T]
}

def isAllEvens[T <: HandPoints](score: T)(cards: List[Card]): T = {
  scored(score) {
    cards.forall(_.getValue % 2 == 0)
  }
}

// 其余函数同理修改

该方案彻底移除了泛型参数的重复声明,唯一的小瑕疵是需要一次类型转换,适合追求极简代码结构的场景。

方案3:按类型分组封装函数

将计分函数封装到带类型参数的特质中,一次性声明类型约束,再为每种HandPoints类型创建对应的实现对象:

trait CardScorer[T <: HandPoints[T]] {
  def scored(score: T)(boolean: Boolean): T = {
    if (boolean) score else score.zero
  }

  def isAllEvens(score: T)(cards: List[Card]): T = {
    scored(score) {
      cards.forall(_.getValue % 2 == 0)
    }
  }

  def isAllReds(score: T)(cards: List[Card]): T = {
    scored(score) {
      cards.forall(c => List("HEARTS", "DIAMONDS").contains(c.getSuit))
    }
  }

  def isAllNoDuplicates(score: T)(cards: List[Card]): T = {
    scored(score) {
      cards.distinct == cards
    }
  }

  def countScore(scoreList: List[List[Card] => T])(melds: List[Card]): T = {
    scoreList.map(f => f(melds)).reduce((a, b) => a.add(b))
  }
}

// 为每种积分类型创建对应的计分器对象
object RegularCardScorer extends CardScorer[RegularPoint]
object DoublingCardScorer extends CardScorer[DoublingPoints]

使用时直接调用对应对象的方法:

val regularGameCriteria: List[List[Card] => RegularPoint] = List(
  RegularCardScorer.isAllEvens(RegularPoint(1)),
  RegularCardScorer.isAllReds(RegularPoint(3)),
  RegularCardScorer.isAllNoDuplicates(RegularPoint(5))
)

def regularGameScore(cards: List[Card]): RegularPoint = {
  RegularCardScorer.countScore(regularGameCriteria)(cards)
}

这种方案完全消除了重复的类型约束,同时按类型分组管理计分逻辑,适合规则复杂、需要扩展更多计分类型的场景。


内容的提问来源于stack exchange,提问作者eancu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 08:55:25