Хороший метод, чтобы было очевидно, что переопределенный метод должен вызывать super? - PullRequest
2 голосов
/ 08 сентября 2011

Эта проблема только что укусила меня, довольно просто забыть вызвать super () при переопределении метода.

В моем случае я проводил рефакторинг некоторых существующих вещей, где уже было около десяти классов, переопределяющих метод. До вчерашнего дня метод имел пустую реализацию по умолчанию, поэтому не имело значения, вызывали ли подклассы супер или нет. Вы можете легко найти оверрайдеры с любой интегрированной средой разработки, которая стоит своей соли, но вы знаете, как это происходит, звонит телефон, у коллеги есть разговор за спиной ... легко забыть проверить место или не заметить его. В идеале должен быть аналог для аннотации @Override, и компилятор сгенерирует предупреждение для этих мест, если аннотируется метод базового класса, но переопределение не вызывает super.

Какая следующая лучшая вещь, которую я могу сделать?

Ответы [ 6 ]

10 голосов
/ 08 сентября 2011

Не совсем элегантное, но возможное решение состоит в том, чтобы разбить этот метод на два:

public abstract class Foo {
    public void doBar() {
        // do your super logic
        doBarInternal();
    }

    public abstract void doBarInternal();
}
3 голосов
/ 08 сентября 2011

Если реализация суперкласса ДОЛЖНА быть вызвана всегда, вы можете использовать шаблон 'template method'.

Итак, то, что у вас есть сейчас, выглядит примерно так:

public static class Parent {
    public void doSomething() {
        System.out.println("Parent doing something");
    }
}

public static class Child extends Parent {
    public void doSomething() {
        // MUST NOT FORGET SUPER CALL
        super.doSomething();
        System.out.println("Child doing something");
    }
}

public static void main(String[] args) {
    Child child = new Child();
    child.doSomething();
}

И это станет:

public abstract static class Parent {
    public final void doSomething() {
        System.out.println("Parent doing something");
        childDoSomething();
    }

    public abstract void childDoSomething();
}

public static class Child extends Parent {
    public void childDoSomething() {
        System.out.println("Child doing something");
    }
}

public static void main(String[] args) {
    Child child = new Child();
    child.doSomething();
}

(классы сделаны статичными для удобства тестирования в одном классе)

Я сделал doSomething финальным, чтобы избежать его переопределения, поскольку в этом решении вместо этого должен быть реализован childDoSomething.

Конечно, это решение означает, что Parent больше не может использоваться как конкретный класс.

РЕДАКТИРОВАТЬ: после прочтения комментариев о дочернем реализации стороннего интерфейса; это не должно быть проблемой:

public interface ThirdPartyInterface {
    public void doSomething();
}

public abstract static class Parent {
    public final void doSomething() {
        System.out.println("Parent doing something");
        childDoSomething();
    }

    public abstract void childDoSomething();
}

public static class Child extends Parent implements ThirdPartyInterface{
    public void childDoSomething() {
        System.out.println("Child doing something");
    }

//    public final void doSomething() {
//        // cannot do this because Parent makes it final
//    }
}

public static void main(String[] args) {
    Child child = new Child();
    child.doSomething();
}    
2 голосов
/ 19 сентября 2011

Поиск чего-то другого я нашел интересным OverrideMustInvoke аннотация в FindBugs : http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/OverrideMustInvoke.html

1 голос
/ 08 сентября 2011

У меня есть 2 разных предложения:

1) Создайте тест junit, который обнаруживает все подклассы вашего базового класса, а затем выбирает все методы, украшенные аннотациями @Override. У меня есть несколько модульных тестов, которые делают что-то похожее (например, найти все подклассы, проверьте, что они действительно , например, Serializable).

К сожалению, проверка того, называют ли они «супер», немного проще. Вам нужно было бы либо чтобы тест проверял исходный файл и искал его, либо лучше (но я не знаю, как это сделать), читал байт-код и проверял, можете ли вы обнаружить вызов super.

2) Необходимость гарантировать вызов super , вероятно, является индикатором проблемы дизайна / интерфейса, а не проблемы кодирования / реализации. Если вы действительно хотите гарантировать, что пользователи будут вызывать super, было бы лучше сделать суперкласс abstract , четко обозначить абстрактный метод реализации, которым они должны переопределить, и иметь супер контроль потока выполнения. 1013 *

Если вы хотите определить реализацию по умолчанию, так что не всем пользователям нужно, чтобы подкласс предоставлял реализацию этого метода, вы можете определить класс реализации по умолчанию для использования людьми. (И если вы действительно хотите управлять им, определите этот метод реализации класса по умолчанию как final , чтобы заставить их вернуться к созданию подкласса абстрактного родителя.)

С наследованием повторного использования кода всегда сложнее управлять, и поэтому его нужно делать осторожно. Любой, кто делает наследование повторного использования кода, должен иметь хорошее представление о внутренностях суперкласса, чтобы сделать это правильно (что немного отвратительно). Например, вам нужно вызывать super.method () в начале вашего переопределенного кода или в конце? (или вы могли бы сделать это в середине ...)

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

1 голос
/ 08 сентября 2011

Если вы не настаиваете на безопасности времени компиляции, вы можете использовать механизм, который выдает исключение, когда подкласс не ведет себя так, как логически требуется: Как заставить полиморфный вызов к методу super?

0 голосов
/ 08 сентября 2011

Что может сработать - создание некоторого маркера аннотации (т.е. @MustCallParent), а затем создание некоторого процессора аннотаций для проверки того, что помеченные им методы соответствуют ограничению.

...