Я видел обсуждение этого вопроса относительно того, как будет реализован класс, реализующий интерфейс. В моем случае я пишу очень маленькую программу на Java, которая использует экземпляр TreeMap
, и, по мнению каждого, она должна создаваться следующим образом:
Map<X> map = new TreeMap<X>();
В моей программе я вызываю функцию map.pollFirstEntry()
, которая не объявлена в Map
интерфейсе (и пару других, которые также присутствуют в Map
интерфейсе). Мне удалось сделать это, приведя к TreeMap<X>
везде, где я называю этот метод, как:
someEntry = ((TreeMap<X>) map).pollFirstEntry();
Я понимаю преимущества рекомендаций по инициализации, описанных выше для больших программ, однако для очень маленькой программы, где этот объект не будет передан другим методам, я думаю, что это не нужно. Тем не менее, я пишу этот пример кода как часть рабочего приложения, и я не хочу, чтобы мой код выглядел плохо и не загромождался. Какое будет самое элегантное решение?
РЕДАКТИРОВАТЬ: Я хотел бы отметить, что меня больше интересуют широкие хорошие практики кодирования, а не применение конкретной функции TreeMap
. Как уже указывалось в некоторых ответах (и я отметил, что ответили первыми, кто это сделал), следует использовать более высокий возможный уровень абстракции, не теряя при этом функциональности.
источник
Ответы:
«Программирование на интерфейсе» не означает «использование максимально абстрактной версии». В этом случае все будут просто использовать
Object
.Это означает, что вы должны определять свою программу по отношению к минимально возможной абстракции, не теряя функциональности . Если вам требуется,
TreeMap
то вам нужно будет определить контракт с помощьюTreeMap
.источник
TreeMap
в качестве примера. «Программа для интерфейса» не должна восприниматься буквально как интерфейс или абстрактный класс - реализация также может рассматриваться как интерфейс.public interface
вместо «открытых методов реализации». ,TreeMap
была бы вышеSortedMap
илиNavigableMap
Если вы все еще хотите использовать интерфейс, вы можете использовать
нет необходимости всегда использовать интерфейс, но часто есть момент, когда вы хотите принять более общий взгляд, который позволяет заменить реализацию (возможно, для тестирования), и это легко, если все ссылки на объект абстрагируются как тип интерфейса
источник
Смысл кодирования интерфейса, а не реализации, состоит в том, чтобы избежать утечки деталей реализации, которые в противном случае ограничивали бы вашу программу.
Рассмотрим ситуацию, когда оригинальная версия вашего кода использовала
HashMap
и разоблачила это.Это означает, что любое изменение
getFoo()
является критическим изменением API и сделает людей, использующих его, несчастными. Если все, что вы гарантируете,foo
это Карта, вы должны вернуть ее.Это дает вам гибкость в изменении того, как все работает внутри вашего кода. Вы понимаете, что на
foo
самом деле должна быть Карта, которая возвращает вещи в определенном порядке.И это ничего не нарушает для остальной части кода.
Позже вы можете укрепить контракт, который дает класс, ничего не нарушая.
Это углубляется в принцип подстановки Лискова
Поскольку NavigableMap является подтипом Map, эта замена может быть выполнена без изменения программы.
Раскрытие типов реализации затрудняет изменение внутренней работы программы, когда необходимо внести изменения. Это болезненный процесс, и много раз он создает уродливые обходные пути, которые служат только для того, чтобы потом причинить боль больший кодер (я смотрю на вас, предыдущий кодер, который по какой-то причине продолжал перетасовывать данные между LinkedHashMap и TreeMap - доверяйте мне всякий раз, когда я вижу твое имя в свн виноват я переживаю).
Вы все еще хотите избежать утечек типов реализации. Например, вы можете захотеть реализовать ConcurrentSkipListMap вместо этого из-за некоторых характеристик производительности или вам просто нравится,
java.util.concurrent.ConcurrentSkipListMap
а неjava.util.TreeMap
в операторах импорта или что-то еще.источник
Согласившись с другими ответами, вам следует использовать наиболее общий класс (или интерфейс), который вам действительно нужен, в данном случае TreeMap (или, как кто-то предложил NavigableMap). Но я хотел бы добавить, что это, безусловно, лучше, чем использовать его повсюду, что в любом случае будет гораздо более сильным запахом. См. Https://stackoverflow.com/questions/4167304/why-should-casting-be-avoided по некоторым причинам.
источник
Речь идет о сообщении вашего намерения о том, как следует использовать объект. Например, если ваш метод ожидает
Map
объект с предсказуемым порядком итераций :Затем, если вам абсолютно необходимо сообщить вызывающим сторонам описанного выше метода, что он тоже возвращает
Map
объект с предсказуемым порядком итераций , потому что по некоторым причинам такое ожидание существует:Конечно, вызывающие могут по-прежнему обрабатывать возвращаемый объект как
Map
таковой, но это выходит за рамки вашего метода:Делая шаг назад
Общие рекомендации по кодированию для интерфейса (в общем) применимы, потому что обычно именно интерфейс обеспечивает гарантию того, что объект должен быть в состоянии выполнить, то есть контракт . Многие начинающие начинают с того, что
HashMap<K, V> map = new HashMap<>()
им советуют объявить это как aMap
, потому что aHashMap
не предлагает ничего больше, чем то, чтоMap
предполагается сделать. Исходя из этого, они смогут понять (надеюсь), что их методы должны приниматьMap
вместо aHashMap
, и это позволяет им реализовать функцию наследования в ООП.Процитирую всего одну строчку из статьи Википедии о любимом всем принципе, связанном с этой темой:
Другими словами, использование
Map
объявления не потому, что оно имеет смысл «синтаксически», а, скорее, вызовы объекта должны заботиться только о его типеMap
.Более чистый код
Я считаю, что это иногда позволяет мне писать более чистый код, особенно когда речь идет о модульном тестировании. Создание
HashMap
с одной тестовой записью, доступной только для чтения, занимает больше строки (исключая использование инициализации с двойной скобкой), когда я могу легко заменить это наCollections.singletonMap()
.источник