Должен ли интерфейс, унаследованный от базового класса, явно реализовываться в подклассе?

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

Например, если вы хотите написать класс, который выполняет контракт интерфейса java.util.List. Вы реализуете это, расширяя класс java.util.AbstractList, который уже реализует интерфейс List. Вы явно заявляете, что вы реализуете List?

public class MyList extends AbstractList implements List

Или вы сохраняете ввод, используя неявный способ?

public class MyList extends AbstractList

Какой способ считается лучшим стилем? Какие причины вы предпочитаете так или иначе? В каких ситуациях вы предпочитаете путь 1 или путь 2?

Ответы

Ответ 1

Я спросил тот же самый вопрос в моем блоге. Существует также длинная дискуссия, если вы заинтересованы в том, чтобы увидеть мысли других людей. Интересно отметить, что обе стратегии взяты в рамках JDK.

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

Ответ 2

Избегайте избыточности. Используйте метод 2.

Используйте @Override для переопределения.

Ответ 3

Это не "стиль". Если вы расширяете AbstractList, который уже реализует List, вам не нужно явно реализовать List.

Это не просто вопрос сохранения набора. Поскольку дополнительный "список инструментов" избыточен, кто-то может потратить некоторое время, пытаясь понять, почему он там. Вы, по сути, пишете в гарантии, что уже часть языка.

Ответ 4

Если вы заботитесь о том, чтобы пользователь вашего класса мог получить список реализованных интерфейсов через Class.getInterfaces(), то ваш класс должен упоминать все интерфейсы в своем "списке инструментов". Class.getInterfaces() не вернет интерфейсы, реализованные суперклассами. Следующая программа будет печатать 1, а затем 0.

import java.io.Serializable;

class Test implements Serializable {
    class Inner extends Test {
    }

    public static void main(String[] argv) throws Exception {
        System.out.println(Test.class.getInterfaces().length);
        System.out.println(Inner.class.getInterfaces().length);
    }
}

Ответ 5

Я бы сказал более вербально. Почему другие, читающие ваш код, должны искать то, что класс реализует? Это делает его понятным для наследуемых функциональных возможностей вашего класса. Но я постараюсь поддерживать согласованность во всем коде в проекте. Это будет путать, если вы делаете это только в одном месте, но если вы будете делать это последовательно, они будут знать об избыточности.

Боковое примечание. На Java существует так много классов, и кто их знает? Тем более, кто знает, из каких классов каждый класс реализует? Вы набрали еще пару секунд, чтобы сохранить разработчика за минуту или два класса.

Ответ 6

Другие комментаторы говорят, что вам следует избегать избыточности, и я согласен с ними.

Чтобы ответить на общий вопрос, единственный случай, о котором я могу думать, для того, чтобы заявить об этом, будет, если бы вы расширили (допустим) AbstractFrotzer и реализовали Frobner, AbstractFrotzer реализовали Frobner, но у вас были основания полагать, что это может не произойти в будущем, Если AbstractFrotzer действительно абстрактно, то он может даже не реализовать все методы Frobner. Это означает, что кто-то может изменить контракт для AbstractFrotzer без вашего изменения вашего класса или любого другого кода, который полагался бы на ваш класс, чтобы быть Frobner. Однако я бы счел это очень редким обстоятельством, и даже если бы это произошло, и ваш класс только расширил AbstractFrotzer, было бы достаточно легко дать ему быструю семантическую проверку и, при необходимости, добавить к нему предложение инвентаря эта точка.

Ответ 7

В то время как я обычно придерживаюсь опции 2, я пойду на конечность и утвержу, что вариант 1 действительно подходит в большинстве случаев, когда он применяется.

Полезность и понятность кода важны. Вопрос, который мы должны задать себе, заключается в том, будет ли разработчик использовать или видеть ссылку на этот класс, интуитивно понять, что класс реализует этот интерфейс (и, следовательно, по существу является подтипом).

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

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

Ответ 8

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

Ответ 9

Когда вы расширяете AbstractList, MyList уже относится к "типу", поэтому нет необходимости явно связывать (или реализовывать) интерфейс.

Ответ 10

Как правило, используйте опцию 2, а не вариант 1.

Вариант 1 не дает никакого дополнения к разработчику или тому, кто будет поддерживать код, в подавляющем большинстве случаев.

Если кто-то действительно хочет знать все корни определенного класса, любая IDE должна быть в состоянии сделать это для вас просто. (Eclipse: ctrl + shift + G)

Что делать, если AbstractList решит не реализовывать List больше? Это не произойдет для подавляющего большинства общих классов. Что относительно других менее очевидных (менее надежных)? Конечно, могут быть исключения, но очень немногие (< 1%?)

Ответ 11

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

Что делать, если базовый класс реализует несколько интерфейсов? Вы тогда явно реализуете все эти интерфейсы и полностью поддерживаете цепочку наследования? Очевидно, нет, поэтому я считаю, что несогласованность этого подхода оказывается неправильной. Намерение может передаваться по-разному, и это не лучший подход ИМХО.