Groovy: есть ли способ реализовать множественное наследование при использовании проверки типов?

@groovy.transform.TypeChecked
abstract class Entity {
    ...
    double getMass() {
        ...
    }
    ...
}

@groovy.transform.TypeChecked
abstract class Location {
    ...
    Entity[] getContent() {
        ...
    }
    ...
}

@groovy.transform.TypeChecked
abstract class Container {...}  //inherits, somehow, from both Location and Entity

@groovy.transform.TypeChecked
class Main {
    void main() {
        double x
        Container c = new Chest() //Chest extends Container
        Entity e = c
        x = e.mass
        Location l = c
        x = l.content  //Programmer error, should throw compile-time error
    }
}

По сути, есть способ добиться этого, не жертвуя каким-либо из трех свойств, описанных в main():

  • Прямой доступ к полям, даже к виртуальным полям
  • Отнесение к обоим суперклассам
  • Проверка типов (во время компиляции)

person Callid    schedule 30.01.2014    source источник


Ответы (1)


Я не думаю, что вы можете сделать это с классами. Возможно, вам нужны черты ( в стадии обсуждения update: доступно в Groovy 2.3 и уже работает!) или, для чисто динамического решения, @Mixin, которое вы бы поддержали с помощью хорошего набора тестов.

Мое предположение: @Delegate - ваш лучший друг здесь, но в его нынешнем виде вы можете хранить только объект Chest в переменной типа Container. Так что вам понадобятся интерфейсы.

Даже если суперкласс не находится под вашим контролем, вы можете использовать Groovy оператор as, чтобы он реализовал интерфейс.

Во-первых, я переписал ваши классы, чтобы удалить abstract и добавить интерфейсы:

import groovy.transform.TypeChecked as TC

interface HasMass { double mass }
interface HasContent { Entity[] getContent() }

@TC class Entity implements HasMass { double mass }

@TC class Location {
    Entity[] getContent() {
        [new Entity(mass: 10.0), new Entity(mass: 20.0)] as Entity[]
    }
}

Обратите внимание, что я не добавил HasContent к Location, чтобы показать использование as.

Во-вторых, идут Container и Chest. @Delegate добавлен и автоматически наследует интерфейсы делегатов:

@TC 
abstract class Container {
  @Delegate Location location = new Location()
  @Delegate Entity entity = new Entity()
}


@TC class Chest extends Container { }

Наконец, он становится проверяемым по типу, пока вы придерживаетесь интерфейсов:

@TC class Mult {
    static main(args) {
        def x // use 'def' for flow-typing
        Container c = new Chest() //Chest extends Container
        HasMass e = c
        x = e.mass
        def l = c as HasContent

        x = l.content  //Programmer error, should throw compile-time error

        assert c.content.collect { Entity it -> it.mass } == [10.0, 20.0]
    }
}
person Will    schedule 30.01.2014
comment
Есть ли способ получить делегат (в данном примере - контейнер) изнутри делегата, или мне нужно, чтобы переменная указывала на него обратно? - person Callid; 13.02.2014
comment
Извините за задержку. Я не знаю, как получить делегат. Если бы это было @Mixin решение, возможно, вы могли бы использовать metaClass.owner. - person Will; 19.02.2014