Как ссылаться на подкласс из статического метода суперкласса в Groovy - PullRequest
0 голосов
/ 03 марта 2019

Упрощенная версия того, что я пытаюсь сделать в Groovy:

class Animal {
    static def echo() {
        println this.name  // ie "class.name"
    }
}

class Dog extends Animal {
}

class Cat extends Animal {
}

Dog.echo()
Cat.echo()

// Output:
//  => Animal
//  => Animal
//
// What I want:
//  => Dog
//  => Cat

Я думаю, что я спрашиваю здесь: когда я вызываю статический метод для объекта и статический методопределен в суперклассе объекта, есть ли способ получить фактический тип объекта?

1 Ответ

0 голосов
/ 04 марта 2019

Статический метод определяется не в контексте объекта, а в контексте класса.Вас может смущать присутствие this в статическом методе Groovy.Тем не менее, это только синтаксический сахар, который в конечном итоге заменяет this.name на Animal.class.name.

Если вы скомпилируете класс Animal из вашего примера с включенной статической компиляцией, вы увидите, что он компилируется в следующий эквивалент Java (результат после декомпиляции файла .class ):

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;

public class Animal implements GroovyObject {
    public Animal() {
        MetaClass var1 = this.$getStaticMetaClass();
        this.metaClass = var1;
    }

    public static Object echo() {
        DefaultGroovyMethods.println(Animal.class, Animal.class.getName());
        return null;
    }
}

Вы можете видеть, что следующая строка в методе echo:

DefaultGroovyMethods.println(Animal.class, Animal.class.getName());

работает непосредственно с именем класса Animal.Таким образом, с точки зрения метода echo, не имеет значения, сколько классов его расширяют.Пока эти классы вызывают метод echo, определенный в классе Animal, вы всегда будете видеть Animal, напечатанный в результате.

И есть даже больше, чем это.Если вы используете следующий скрипт конфигурации компилятора:

config.groovy

withConfig(configuration) {
    ast(groovy.transform.CompileStatic)
    ast(groovy.transform.TypeChecked)
}

и затем скомпилируете скрипт (назовем его script.groovy ) используя этот параметр конфигурации с помощью следующей команды:

groovyc --configscript=config.groovy script.groovy

, после декомпиляции файла .class вы увидите нечто подобное:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import groovy.lang.Binding;
import org.codehaus.groovy.runtime.InvokerHelper;

public class script extends groovy.lang.Script {
    public script() {
    }

    public script(Binding context) {
        super(context);
    }

    public static void main(String... args) {
        InvokerHelper.runScript(script.class, args);
    }

    public Object run() {
        Animal.echo();
        return Animal.echo();
    }
}

Youвидно, что даже если вы вызвали Dog.echo() и Cat.echo() в своем скрипте Groovy, компилятор заменил эти вызовы двойным вызовом Animal.echo().Это произошло потому, что вызов этого статического метода для любого другого подкласса не имеет значения.

Возможное решение: применение двойной диспетчеризации

Существует один способ получить ожидаемый результат - переопределить echo staticметод в Dog и Cat классе.Я могу предположить, что ваш реальный метод может сделать что-то большее, чем примерный метод echo, который вы показали выше, поэтому вам может потребоваться вызвать метод super echo из родительского класса.Но ... есть две проблемы: (1) вы не можете использовать super.echo() в статическом контексте, и (2) это не решает проблему, потому что родительский метод все еще работает в контексте класса Animal.'

Чтобы решить эту проблему, вы можете имитировать технику, называемую double dispatch .Вкратце - когда у нас нет информации о вызывающем объекте в вызываемом методе, давайте разрешим вызывающему передать эту информацию с помощью вызова метода.Рассмотрим следующий пример:

import groovy.transform.CompileStatic

@CompileStatic
class Animal {
    // This is a replacement for the previous echo() method - this one knows the animal type from a parameter
    protected static void echo(Class<? extends Animal> clazz) {
        println clazz.name
    }

    static void echo() {
        echo(Animal)
    }
}

@CompileStatic
class Dog extends Animal {
    static void echo() {
        echo(Dog)
    }
}

@CompileStatic
class Cat extends Animal {
    static void echo() {
        echo(Cat)
    }
}

Animal.echo()
Dog.echo()
Cat.echo()

Это может звучать как шаблонное решение - для него требуется реализация метода echo в каждом подклассе.Однако он инкапсулирует логику echo в методе, для которого требуется параметр Class<? extends Animal>, поэтому мы можем позволить каждому подклассу вводить свой конкретный подтип.Конечно, это не идеальное решение.Это требует реализации метода echo в каждом подклассе, но другого альтернативного пути нет.Другая проблема заключается в том, что это не мешает вам звонить Dog.echo(Animal), что вызовет тот же эффект, что и Animal.echo().Этот подход с двойной диспетчеризацией больше похож на введение сокращенной версии метода echo, в которой для простоты используется стандартная реализация метода echo.

Я не знаю, решает ли этот подход вашу проблему,но, возможно, это поможет вам найти окончательное решение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...