Улучшить классы java, используя черты, как объявить внутри свойства java-поля?

Моя цель состоит в том, чтобы улучшить внутри scala код существующего Java-класса с использованием сочетания признаков. Например, чтобы добавить метод java.awt.Rectangle.translate(dx, dy) в класс java.awt.geom.Ellipse2D. Для этого я создаю следующий признак:

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 

Затем используйте свойство при построении эллипса:

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

Однако, когда я выполняю приведенный выше script в scala REPL, я получаю следующий вывод:

<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

Я подозреваю, что эта ошибка связана с тем, как scala реализует vars - как частное поле и пару методов getter/setter. Я пытаюсь достичь выполнимости? Есть ли другой способ определить поля класса java в признаке, а затем обратиться к ним внутри методов конкретных признаков?

Спасибо заранее Джек Димас

Ответ 1

Да, это выполнимо, но вместо того, чтобы пытаться получить доступ к закрытым полям классов, с которыми вы хотите смешать (что, скорее всего, будет плохой идеей), вы хотели бы объявить тип типа RectangleLike быть java.awt.geom.RectangularShape, чтобы вы могли использовать свою черту, например Ellipse2D.Double так же, как и с Rectangle2D.Double.

Вот как это работает:

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
}

Говоря self: java.awt.geom.RectangularShape =>, вы объявляете свой тип своего свойства, который позволяет вам получить доступ ко всем соответствующим методам, таким как необходимые геттеры и сеттеры, позволяет использовать вашу черту со всеми потомками RectangularShape, а также "ограничивает", ваш признак, чтобы его можно было использовать только как mixin для классов, которые сами являются подтипами RectangularShape.

альтернативный для вышеупомянутого сценария использует так называемый вид вашего RectangularShape, который также является общей парадигмой. Для этого вы, например, объявить класс

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 имеет способ неявного просмотра объекта одного типа как объекта другого типа. Если вы вызываете метод для объекта, который не объявлен в соответствующем типе, компилятор пытается найти тип, который предоставляет этот метод, и, в частности, также пытается найти неявное преобразование, чтобы ваш исходный объект можно было рассматривать как экземпляр последнего типа. Чтобы это сработало, вы обычно объявляете объект-компаньон RichRectangularShape следующим:

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

Тогда:

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

Ответ 2

Действительно впечатляющее объяснение и пример!

У меня есть одна небольшая проблема с этим подходом, метод translate включает реальные вычисления, которые я бы хотел избежать. Да в этом случае это очень просто, но, вообще говоря, такой метод может быть сложным и привести к серьезному развитию. Поэтому я предлагаю следующий подход:

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)
      }
}

Таким образом, я использую исходный расчет translate.