Улучшить Java-классы, используя черты, как объявить внутри черты Java поля? - PullRequest
10 голосов
/ 07 февраля 2012

Моя цель - улучшить внутри 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

Однако, когда я выполняю вышеуказанный скрипт в 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 реализует переменные - в качестве частного поля и пары методов получения / установки.Это то, что я пытаюсь достичь выполнимо?Есть ли другой способ определить поля класса java в признаке и затем обратиться к ним внутри конкретных методов признака?

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

Ответы [ 2 ]

8 голосов
/ 07 февраля 2012

Да, это выполнимо, но вместо того, чтобы пытаться получить доступ к закрытым полям классов, с которыми вы хотите смешаться (что, скорее всего, плохая идея в любом случае), вы захотите объявить собственный тип 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, а также «ограничивает» Ваша черта, так что он может быть использован только как миксин для классов, которые сами являются подтипами 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 = java.awt.geom.Ellipse2D$Double@0

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
0 голосов
/ 19 апреля 2017

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

У меня есть одна небольшая проблема с этим подходом, метод 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 расчет.

...