【问题标题】:How can you inherit a generic factory method?如何继承泛型工厂方法?
【发布时间】:2011-03-23 10:35:15
【问题描述】:

假设你有一个 Person 类,并通过扩展例如为它创建一个集合类。数组缓冲区:

class Persons extends ArrayBuffer[Person] {
// methods operation on the collection
}

现在,使用 ArrayBuffer,可以在伴生对象上使用 apply() 方法创建一个集合,例如:

ArrayBuffer(1, 2, 3)

您希望能够对 Persons 做同样的事情,例如:

Persons(new Person("John", 32), new Person("Bob", 43))

我的第一个直觉是扩展 ArrayBuffer 伴生对象并免费获得 apply() 方法。但似乎你不能扩展对象。 (我不太清楚为什么。)

下一个想法是使用调用 apply 的 apply() 方法创建一个 Persons 对象 ArrayBuffer的方法:

object Persons {
    def apply(ps: Person*) = ArrayBuffer(ps: _*) 
}

但是,这会返回一个 ArrayBuffer[Person] 而不是 Persons。

在对 ArrayBuffer 的 scaladoc 和源代码进行了一些挖掘之后,我想出了以下内容,我认为这会使 Persons 对象从 GenericCompanion 继承 apply():

编辑:

object Persons extends SeqFactory[ArrayBuffer] {
    def fromArrayBuffer(ps: ArrayBuffer[Person]) = {
        val persons = new Persons
        persons appendAll ps
        persons
    }

    def newBuilder[Person]: Builder[Person, Persons] = new ArrayBuffer[Person] mapResult fromArrayBuffer
}

但是,它给出了以下错误消息:

<console>:24: error: type mismatch;
 found   : (scala.collection.mutable.ArrayBuffer[Person]) => Persons
 required: (scala.collection.mutable.ArrayBuffer[Person(in method newBuilder)])
=> Persons
        def newBuilder[Person]: Builder[Person, Persons] = new ArrayBuffer[Perso
n] mapResult fromArrayBuffer
             ^

也许这会阻止我更进一步,但我在学习 Scala 时玩得很开心,我真的很想让它发挥作用。如果我走错了路,请告诉我。 :)

【问题讨论】:

  • 你不能扩展一个对象,因为它定义了一个实例而不是一个类型。这个类比可能会有所帮助——您可以拥有 SoftwareEngineer 的子类别(例如 JavaHacker),但谈论 Martin Fowler 的子类别是没有意义的。同理,扩展实例也没有意义。
  • 我认为在这里扩展是一个非常糟糕的主意。 人是一种特殊的 ArrayBuffer 吗?不,所以你显然应该更喜欢这里的构图。
  • 这是一个有趣的观点,值得自己讨论,但我认为这是一个比实际更哲学的观点。 (即,为什么在实践中这是一个坏主意。)您可以说 Persons 是一系列人员,但是 Seq 是抽象的,因此您可以从具体实现 ArrayBuffer 继承。
  • 在这种情况下,继承在实践中是一个坏主意,因为它将您与特定的集合实现联系在一起。假设您发现对于特定的一组人,您需要能够快速(即在恒定时间内)确定一个人是否在该组中。显然,对于这个用例,ArrayBuffer 将是一个糟糕的选择。另一方面,如果您使用组合,则 Persons 类中的方法可以同样适用,无论基础集合是 Set 还是 ArrayBuffer。
  • 不,你可以使用 SeqProxy trait 免费获得这些方法:class Persons (val self: Seq[Person]) extends SeqProxy[Person]

标签: scala


【解决方案1】:

您可以使用pimp my library pattern,而不是直接扩展ArrayBuffer[Person]。这个想法是让PersonsArrayBuffer[Person] 完全可以互换。

class Persons(val self: ArrayBuffer[Person]) extends Proxy {
   def names = self map { _.name }

   // ... other methods ...
}

object Persons {
   def apply(ps: Person*): Persons = ArrayBuffer(ps: _*)

   implicit def toPersons(b: ArrayBuffer[Person]): Persons = new Persons(b)

   implicit def toBuffer(ps: Persons): ArrayBuffer[Person] = ps.self
}

Persons 伴随对象中的隐式转换允许您在有 Persons 引用时使用任何 ArrayBuffer 方法,反之亦然。

例如,你可以这样做

val l = Persons(new Person("Joe"))
(l += new Person("Bob")).names

注意l 是一个Persons,但你可以在它上面调用ArrayBuffer.+= 方法,因为编译器会自动添加对Persons.toBuffer(l) 的调用。 += 方法的结果是 ArrayBuffer,但您可以在其上调用 Person.names,因为编译器会插入对 Persons.toPersons 的调用。

编辑:

您可以将此解决方案推广到更高种类的类型:

class Persons[CC[X] <: Seq[X]](self: CC[Person]) extends Proxy {
   def names = self map (_.name)
   def averageAge = {
      self map (_.age) reduceLeft { _ + _ } / 
            (self.length toDouble)
   }
   // other methods
}

object Persons {
   def apply(ps: Person*): Persons[ArrayBuffer] = ArrayBuffer(ps: _*)

   implicit def toPersons[CC[X] <: Seq[X]](c: CC[Person]): Persons[CC] =
         new Persons[CC](c)

   implicit def toColl[CC[X] <: Seq[X]](ps: Persons[CC]): CC[Person] = 
         ps.self
}

这可以让你做类似的事情

List(new Person("Joe", 38), new Person("Bob", 52)).names

val p = Persons(new Person("Jeff", 23))
p += new Person("Sam", 20)

请注意,在后一个示例中,我们在 Persons 上调用 +=。这是可能的,因为Persons“记住”了基础集合类型,并允许您调用在该类型中定义的任何方法(在这种情况下为ArrayBuffer,由于Persons.apply 的定义)。

【讨论】:

  • 皮条客我的图书馆链接失效了
【解决方案2】:

除了anovstrup的解决方案,下面的例子难道不是你想要的吗?


case class Person(name: String, age: Int)

class Persons extends ArrayBuffer[Person]

object Persons {
    def apply(ps: Person*) = {
      val persons = new Persons
      persons appendAll(ps)
      persons
    }
}

scala> val ps = Persons(new Person("John", 32), new Person("Bob", 43))
ps: Persons = ArrayBuffer(Person(John,32), Person(Bob,43))

scala> ps.append(new Person("Bill", 50))   

scala> ps
res0: Persons = ArrayBuffer(Person(John,32), Person(Bob,43), Person(Bill,50))

【讨论】:

  • 我知道这个解决方案,但我认为继承apply方法会更优雅。 object ArrayBuffer 本身并没有定义它的 apply 方法,而是从 GenericCompanion 继承它,或者看起来,如果我错了,请纠正我。不过,这似乎是解决原始问题的最简单实用的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-25
相关资源
最近更新 更多