2010-07-30 14 views
7

Supongamos que tiene una Persona de clase y crea una clase de colección extendiéndola, p. ArrayBuffer:¿Cómo se puede heredar un método de fábrica genérico?

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

Ahora, con ArrayBuffer, puede crear una colección con el método de aplicación() en el objeto acompañante, por ejemplo:

ArrayBuffer(1, 2, 3) 

¿Quieres ser capaz de hacer lo mismo con las personas, por ejemplo:

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

Mi primera intuición aquí era ampliar el objeto compañero ArrayBuffer y conseguir el método de aplicación() de forma gratuita. Pero parece que no puedes extender objetos. (No estoy seguro de por qué.)

La siguiente idea era crear un objeto Personas con un apply() llama al método que se aplican método de ArrayBuffer:

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

Sin embargo, este devuelve una ArrayBuffer [Persona] y no una Persona.

Después de algo de investigación en el scaladoc y fuente de ArrayBuffer, me ocurrió lo siguiente, lo cual pensé que haría que las personas objeto heredará aplicar() desde GenericCompanion:

EDIT:

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 
} 

sin embargo, se da el siguiente mensaje de error:

<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 
      ^

Tal vez esto me debe desestimulan de ir más lejos, pero estoy teniendo un gran tiempo learnin g Scala y realmente me gustaría que esto funcione. Por favor dime si estoy en el camino equivocado. :)

+2

No se puede extender un objeto porque define una instancia en lugar de un tipo. Esta analogía podría ayudar: puede tener subcategorías de Software Engineer (por ejemplo, JavaHacker), pero no tiene sentido hablar de subcategorías de Martin Fowler. Del mismo modo, no tiene sentido extender una instancia. –

+1

Creo que extender aquí es una muy mala idea. * ¿Son * las personas un tipo especial de ArrayBuffer? No. Entonces, claramente debería preferir la composición aquí. – Landei

+0

Ese es un punto interesante, que podría justificar su propia discusión, pero creo que es más filosófico que práctico. (Es decir, ¿por qué es una mala idea en la práctica?). Podría decir que Persons es una secuencia de personas, sin embargo, Seq es abstracto, por lo que hereda de la implementación concreta ArrayBuffer. –

Respuesta

3

En lugar de extender ArrayBuffer[Person] directamente, puede usar el pimp my library pattern. La idea es hacer que Persons y ArrayBuffer[Person] sean completamente intercambiables.

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 
} 

La conversión implícita en el objeto acompañante Personas le permite utilizar cualquier método ArrayBuffer cada vez que tenga una referencia Personas y viceversa.

Por ejemplo, se puede hacer

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

Tenga en cuenta que es una lPersons, pero se puede llamar al método ArrayBuffer.+= en él porque el compilador añadirá automáticamente en una llamada a Persons.toBuffer(l). El resultado del método += es ArrayBuffer, pero puede llamar al Person.names porque el compilador inserta una llamada al Persons.toPersons.

Editar:

Puede generalizar esta solución con tipos más altos kinded:

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 
} 

Esto le permite hacer cosas como

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

o

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

Tenga en cuenta que en el último ejemplo, estamos llamando al += en un Persons. Esto es posible porque Persons "recuerda" el tipo de colección subyacente y le permite llamar a cualquier método definido en ese tipo (ArrayBuffer en este caso, debido a la definición de Persons.apply).

+0

Ese patrón de reglas de nombres;) –

+0

El enlace pimp my library está muerto – BlackBear

1

Además de la solución de anovstrup, ¿el ejemplo siguiente no hará lo que usted quiere?


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)) 
+0

Soy consciente de esta solución, pero pensé que sería más elegante heredar el método apply. objeto ArrayBuffer no define su método de aplicación, pero lo hereda de GenericCompanion, o al menos eso parece, me corrige si me equivoco. Sin embargo, esta parece ser la solución práctica más simple para el problema original. –

Cuestiones relacionadas