设计模式——Scala为类添加可堆叠属性



我有一个简单的类Feature,目前作为case类实现

case class Feature(value :String)

有多个操作用不同的属性装饰一个特征,例如,有一个函数可能会计算特征的出现次数,所以我可能需要CountedFeature。除了计数,我可能还需要一个WeightedFeature,一个IndexedFeature等。

我的直觉告诉我它适合性状,所以我定义了以下性状

trait Counted {def count :Long}
trait Weighted {def weight :Double}
trait Indexed {def index :Int}

会出现两个问题:1. 我是否需要创建一个具体的类来实现每个特征的组合(例如实现CountedWeightedFeature, CountedIndexedfeature等),或者有一些方法来避免它。如果我转向更多的装饰,就不可能维持所有组合的职业。2. 我想设计一个函数,根据它们的数量来对特征进行加权。它的签名应该看起来像:

def computeWeightsByCount(features :List[T <: Feature with Counted]) :List[T with Weighted] = {...}
这里的

T可能是索引的,也可能不是索引的,所以这个函数应该有一些方法来获取一个类并实例化一个新类,这个新类具有原始类的所有特征,并在里面堆叠一个额外的特征。

在Scala中是否有一些优雅的方法来做到这一点,或者我是否应该完全重新考虑这个设计?

这个设计看起来不错,除了不推荐扩展case类。在这里可以找到原因的简要总结:https://stackoverflow.com/a/12705634/2186890

所以你可能想把Feature重写成这样:

trait Feature { def value: String }

现在你可以为模式匹配等定义case类,像这样:

case class CountedFeature(value: String, count: Long) extends Feature with Counted
没有简单的方法来避免像这样的case类的组合爆炸,但是您可以在任何您喜欢的地方使用像Feature with Counted这样的类型。请记住,您可以轻松地创建与类型Feature with Counted匹配的对象。例如:
val x: Feature with Counted = new Feature with Counted { val value = ""; val count = 0L }

像您想要的那样实现computeWeightsByCount有点棘手,因为在不了解T类型的情况下,没有简单的方法来构建T with Weighted。但它可以用隐式方法来完成。从本质上讲,我们需要有一个定义的路径,以便为您想要应用此方法的每个Feature with CountedT生成T with Weighted。例如,我们这样开始:

trait Feature { def value: String }
trait Counted { def count: Long }
trait Weighted { def weight: Double }
trait Indexed { def index: Int }

我们想要定义computeWeightsByCount,就像你在你的问题中所做的那样,但也采取一个隐式方法,接受T和权重,并产生T with Weighted:

def computeWeightsByCount[
  T <: Feature with Counted](                                                                                       
  features: List[T])(
  implicit weighted: (T, Double) => T with Weighted
): List[T with Weighted] = {  
  def weight(fc: Feature with Counted): Double = 0.0d
  features map { f => weighted(f, weight(f)) }
}

现在我们需要定义一个隐式方法来从输入特征中产生加权特征。让我们从Feature with Counted中获得Feature with Counted with Weighted开始。我们将把它放在Feature的伴侣对象中:

object Feature {
  implicit def weight(fc: Feature with Counted, weight: Double): Feature with Counted with Weighted = {
    case class FCW(value: String, count: Long, weight: Double) extends Feature with Counted with Weighted
    FCW(fc.value, fc.count, weight)
  }
}

我们可以这样使用:

case class FC(value: String, count: Long) extends Feature with Counted
val fcs: List[Feature with Counted] = List(FC("0", 0L), FC("1", 1L))
val fcws: List[Feature with Counted with Weighted] = computeWeightsByCount[Feature with Counted](fcs)

对于任何想要计算加权计数的类型,都需要定义类似的隐式方法。

诚然,这远非一个完美的解决方案。所以,是的,你是对的,你可能需要重新考虑设计。然而,这种方法的优点是,对Feature"层次结构"的任何进一步扩展都可以在不需要对computeWeightsByCount进行任何更改的情况下进行。不管是谁写的新trait,都可以提供合适的隐式方法

最新更新