2012-02-07 15 views
10

Mi objetivo es mejorar el código scala dentro de una clase Java existente utilizando una mezcla de rasgos. Por ejemplo, para agregar un método como java.awt.Rectangle.translate (dx, dy) a la clase java.awt.geom.Ellipse2D. Para ello se crea la siguiente característica:Mejora de las clases de Java utilizando rasgos, ¿cómo declarar dentro de los campos Java?

trait RectangleLike { 
    var x: Double // abstract vals to correspond to java class fields 
    var y: Double // I need these vars to refer to them inside translate method 
    def translate(dx: Double, dy: Double) { 
    x = x + dx 
    y = y + dy 
    } 
    // more concrete trait methods here 
} // defines without errors in scala REPL 

A continuación, utilice el rasgo en la construcción de una elipse:

val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30) with RectangleLike 

Sin embargo, cuando ejecuto el script anterior en REPL Scala me sale el siguiente resultado:

<console>:8: error: overriding variable x in trait RectangleLike of type Double; 
variable x in class Double of type Double has incompatible type; 
other members with override errors are: y 
val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30) with RectangleLike 

Sospecho que este error se debe a la forma en que Scala implementa vars, como un campo privado y un par de métodos getter/setter. Es lo que trato de lograr factible? ¿Hay alguna otra forma de definir los campos de la clase java en el rasgo y luego referirnos a ellos dentro de los métodos de rasgo concreto?

Gracias de antemano Jack Dimas

Respuesta

8

Sí, es factible, pero en lugar de tratar de acceder a los campos privados de las clases que desea mezclar con (que es más probable una mala idea de todos modos), lo haría desea declarar que el tipo de uno mismo de RectangleLike es java.awt.geom.RectangularShape para que pueda usar su rasgo con, por ejemplo, Ellipse2D.Double tan bien como con Rectangle2D.Double.

Así es como funciona:

trait RectangleLike { 
    self: java.awt.geom.RectangularShape => 

    def translate(dx: Double, dy: Double) { 
    setFrame(getX + dx, getY + dy, getX + getWidth, getY + getHeight) 
    } 
} 

object Test { 
    val foo = new java.awt.geom.Ellipse2D.Double with RectangleLike 
} 

Al decir self: java.awt.geom.RectangularShape => se declara la auto-tipo de su rasgo que le permite acceder a todos los métodos correspondientes, como los captadores y definidores necesarias, permite la utilización de su rasgo con todos los descendientes de RectangularShape, y también "restringe" su rasgo para que solo se pueda usar como mixin en clases que a su vez son subtipos de RectangularShape.

El alternativa al escenario anterior es el uso de un denominado vista de su RectangularShape que es un paradigma común también. Para esto, por ejemplo, declarar una clase

class RichRectangularShape(shape: java.awt.geom.RectangularShape) { 
    def translate(dx: Double, dy: Double) { 
    shape.setFrame(shape.getX + dx, shape.getY + dy, 
        shape.getX + shape.getWidth, shape.getY + shape.getHeight) 
    } 
} 

Scala tiene una manera de implícitamente visualización un objeto de un tipo como un objeto de otro tipo. Si llama a un método sobre un objeto que no está declarado en su tipo correspondiente, el compilador intenta encontrar un tipo que proporcione este método y, en particular, también intenta encontrar una conversión implícita para que su objeto original pueda verse como un instancia del último tipo. Para que esto funcione, lo más habitual es declarar el objeto acompañante de RichRectangularShape como algo parecido a esto:

object RichRectangularShape { 
    implicit def mkRRS(shape: java.awt.geom.RectangularShape): RichRectangularShape = 
    new RichRectangularShape(shape) 
} 

continuación:

scala> import RichRectangularShape._ 
import RichRectangularShape._ 

scala> val foo = new java.awt.geom.Ellipse2D.Double 
foo: java.awt.geom.Ellipse2D.Double = [email protected] 

scala> foo.translate(5,2) 

scala> foo.getX 
res1: Double = 5.0 

scala> foo.getY 
res2: Double = 2.0 

scala> :t foo 
java.awt.geom.Ellipse2D.Double 
+1

@forNelton ¡impresionante! Gracias mil. Realmente estaba atrapado en esto. Tengo que estudiar puntos de vista implícitos. – ideathbird

+0

De nada. Dependiendo del tipo de su aplicación, creo que el primer enfoque podría ser más apropiado, ya que es probable que las llamadas al método sean mucho más rápidas sin todas las molestias implícitas. – fotNelton

0

En efecto impresionante explicación y ejemplo!

Tengo un pequeño problema con este enfoque, el método translate incluye el cálculo real que me gustaría evitar. Sí, en este caso es muy simple, pero en general dicho método puede ser complicado y conducir a un desarrollo serio.Por tanto, sugiero el siguiente enfoque:

trait RectangleLike extends java.awt.geom.RectangularShape { 
    def translate(dx: Int, dy: Int): Unit = { 
     val rect = new java.awt.Rectangle 
     rect.setRect(getX, getY, getWidth, getHeight) 
     rect.translate(dx,dy) 
     setFrame(rect.getX, rect.getY, rect.getWidth, rect.getHeight) 
     } 
} 

De esta manera estoy usando el cálculo original translate.

Cuestiones relacionadas