Зачем определять объект Java, используя интерфейс (например, Map), а не реализацию (HashMap)

17

В большинстве кодов Java люди видят объекты Java следующим образом:

Map<String, String> hashMap = new HashMap<>();
List<String> list = new ArrayList<>();

вместо того:

HashMap<String, String> hashMap = new HashMap<>();
ArrayList<String> list = new ArrayList<>();

Почему предпочтительнее определять Java-объект с использованием интерфейса, а не реализации, которая фактически будет использоваться?

Суман
источник

Ответы:

26

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

Следует сказать, что есть исключения из этого, когда реализация актуальна. Если вам нужна карта, когда важен порядок, вы можете потребовать, чтобы a TreeMapили a LinkedHashMapбыли переданы, или, что еще лучше, в SortedMapкоторых не указана конкретная реализация. Это обязывает вызывающему обязательно пройти определенный тип реализации карты и сильно намекает , что порядок является важным. Тем не менее, вы могли бы переопределить SortedMapи передать несортированный? Да, конечно, однако ожидайте, что плохие вещи произойдут в результате.

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

Нил
источник
Когда вам нужна отсортированная карта, тип параметра должен быть SortedMap, а не TreeMap.
Головоногий
@Arian SortedMap- это одна из нескольких реализаций, которые занимаются упорядочением. Это помимо смысла. TreeMapтакже упорядочивает элементы в соответствии с реализацией ключа Comparableили заданным Comparatorинтерфейсом.
Нил
Нет, SortedMap не является реализацией, в этом суть. Это интерфейс для карт, которые сортируются по ключу.
Головоногий
1
@Arian Ах, я понимаю, что ты имеешь в виду. Правда, лучше SortedMap, поскольку это не требует реализации. Я сделаю правильные корректировки.
Нил
На самом деле, LinkedHashMapне реализует SortedMap. Единственными подклассами SortedMapявляются ConcurrentSkipListMapи TreeMap.
bcorso
10

По словам Леймана:

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

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

С точки зрения настенной розетки не имеет значения, подключаете ли вы телевизор или стереосистему.

Это делает и прибор, и розетку более полезными.

Возьмем, к примеру, метод, который принимает карту в качестве аргумента.

Этот метод будет работать независимо от того, передаете ли вы ему HashMap или LinkedHashMap, если это подкласс Map.

Это принцип замещения Лискова .

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

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

Тулаинс Кордова
источник
4

Это должно следовать принципу разделения интерфейса («Я» в ТВЕРДОМ ). Он предотвращает зависимость кода, использующего эти объекты, от методов тех объектов, которые ему не нужны, что делает код менее связанным и, следовательно, более легким для изменения.

Например, если вы узнаете позже, что вам действительно нужно LinkedHashMap, вы можете безопасно внести это изменение, не затрагивая любой другой код.

Однако есть компромисс, потому что вы искусственно ограничиваете код, который может принимать ваш объект в качестве параметра. Скажем , есть функция где - то , что требуетHashMap по какой - то причине. Если вы возвращаете Map, вы не можете передать свой объект в эту функцию. Вы должны уравновесить вероятность того, что когда-нибудь в будущем понадобится дополнительная функциональность, относящаяся к более конкретному классу, с желанием ограничить связывание и сделать ваш публичный интерфейс как можно меньшим.

Карл Билефельдт
источник
3

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

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

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

Джимми Хоффа
источник