2012-03-02 26 views
9

Me gustaría tener un rasgo que pueda a) mezclarse en cualquier clase con un método particular, yb) llamar súper. Algo como esto:Características de Scala y tipos estructurales: ¿puede un rasgo extender un tipo estructural y luego llamar a super?

// A and B are from a library that I don't control. No changes allowed here. 
    class A { 
    def stuff = "a stuff" 
    } 
    class B { 
    def stuff = "b stuff" 
    } 

    // My code starts here 

    type HasStuffMethod = { 
    def stuff: String 
    } 

    // Note that this doesn't compile - gets: 
    // class type required but AnyRef{def stuff: String} found 
    trait ImplementsStuff extends HasStuffMethod { 
    override def stuff = "trait + " + super.stuff 
    } 

    val a = new A with ImplementsStuff 
    assert(a.stuff == "trait + a stuff") 

    val b = new B with ImplementsStuff 
    assert(b.stuff == "trait + b stuff") 

¿Hay alguna manera de hacer esto?

Tenga en cuenta que no controlo A y B; vienen de otra biblioteca que no puedo modificar.

[Editar - añadió después de ver respuestas]

¿Hay alguna manera para llamar al método original en algo como esto?

trait ImplementsStuff { 
    this: HasStuffMethod => 
    abstract override def stuff = "foo" + "how do I call the original method here?" 
    } 

Esto no es útil, ya que cuando se mezclan en algo que da:

error: overriding method stuff in class A of type => java.lang.String; method stuff in trait ImplementsStuff of type => java.lang.String cannot override a concrete member without a third member that's overridden by both (this rule is designed to prevent ``accidental overrides'')

Pero no es casualidad; sí, realmente quiero que recorras todo ese método existente. Y luego déjame llamarlo también.

+2

Aquí 'HasStuffMethod' no es un tipo de clase. Es un alias tipo a un tipo estructural. – paradigmatic

+1

Derecha: un patrón de clase de letra es otra cosa. Y es irrelevante para esta pregunta si 'HasStuffMethod' es un tipo estructural o nominal (es decir, un rasgo regular). – axel22

+1

He agregado una edición propuesta a su pregunta.Espero que aclare lo que estás tratando de hacer. Si he malinterpretado, puedes borrarlo. Si es correcto, puede usarlo para reformular su pregunta. En ese caso, edite también la referencia a "typeclass" en el título de la pregunta. –

Respuesta

4

Necesita un abstract override en tal caso.

+0

¿Cómo defines el rasgo? (Se agregó una nota que indica que la línea "rasgo ImplementsStuff extends HasStuffMethod" no se compila). –

+1

@JamesMoore Just 'abstract override def ...'. Pero me acabo de dar cuenta de que lo tiene como 'tipo '-' tipo' es solo un alias, y su alias es de tipo estructural: eso no funcionará. Haz que se extienda una 'clase abstracta' en su lugar, o un 'rasgo'. –

5

puedo pensar en dos opciones:

la opción 1, utilizando una anotación de tipo auto y llamando stuff de un nuevo método (que con imaginación llamé callStuff) en lugar de anular la misma.

trait ImplementsStuff { 
    this: HasStuffMethod => 
    def callStuff = "trait + " + this.stuff 
    } 

    val a = new A with ImplementsStuff 
    assert(a.callStuff == "trait + a stuff") 

    val b = new B with ImplementsStuff 
    assert(b.callStuff == "trait + b stuff") 

opción 2 (ya que dice que no controla A y B) es el viejo y querido patrón de decorador.

trait HasStuff { def stuff: String } 

    class DecorateStuff(decorated: HasStuffMethod) extends HasStuff { 
    def stuff = "trait + " + decorated.stuff 
    } 
    val decA = new DecorateStuff(new A) 
    assert(decA.stuff == "trait + a stuff") 

    val decB = new DecorateStuff(new B) 
    assert(decB.stuff == "trait + b stuff") 
+0

Ambos interesantes, pero ninguno resuelve el problema. El primero falla porque no hay forma de decirle a todas las personas que llaman que tienen que llamar a un nuevo método (por lo que las afirmaciones definitivamente fallan). El segundo devuelve un tipo diferente, por lo que es una técnica útil, pero en realidad no ayuda aquí. ¿Qué haces cuando necesitas pasar lo nuevo a otro código que espera una A, no una DecorateStuff? –

+0

@JamesMoore Tiene razón, ambos métodos suponen que usted/su equipo controlan el código de la persona que llama. Si ese no es el caso y sus expectativas son cambiar el comportamiento de su método de cosas, pero todavía tienen A y B, esto parece una Programación Orientada a Aspectos. Puedes mirar AspectJ. [Este artículo de Jonas Boner puede ayudarlo] (http://architects.dzone.com/articles/real-world-scala-managing-cros) –

+0

Es solo que el sistema existente parece _muy_ cerca de lo que me gustaría. En tu primer ejemplo, si pudieras cambiar callStuff por cosas simples, y luego tener una manera de hacer referencia al método que se está anulando, lo tendrías. (Agregué eso a mi pregunta) –

1

No hay manera de extender un tipo refinado en Scala (no estoy seguro de que esto podría ser halado con seguridad, pero sería interesante explorar).

Tengo una solución que es más detallada, pero te acerca a tu objetivo.

trait HasStuff { 
    def theirStuff: String 
} 

trait ImplementsStuff extends HasStuff { 
    def stuff = "trait + " + theirStuff 
} 

Tenga en cuenta que, en lugar de un tipo refinado, tengo un rasgo y lo extiendo en la implementación. Necesito agregar súper accesores manualmente, lo hago por delegación. HasStuff los define, y ponerlas en práctica en el lugar de mixin:

val a = new A with ImplementsStuff { 
    def theirStuff = super[A].stuff 

    override def stuff = super[ImplementsStuff].stuff 
} 

val b = new B with ImplementsStuff { 
    def theirStuff = super[B].stuff 

    override def stuff = super[ImplementsStuff].stuff 
} 

En el sitio mixin que necesito para poner en práctica el método de acceso a super (theirStuff) que transmita al método correcto heredada. También necesito anular stuff en este nivel, ya que ImplementsStuff no hereda un método stuff, por lo tanto no puede override.

Calling stuff en a y b muestra se ha anulado:

$ a.stuff 
res0: java.lang.String = trait + a stuff 

$ b.stuff 
res1: java.lang.String = trait + b stuff 
Cuestiones relacionadas