Подтвердить что ты не робот

Когда я должен вернуть интерфейс и когда конкретный класс?

при программировании на Java я практически всегда, просто по привычке, пишу что-то вроде этого:

public List<String> foo() {
    return new ArrayList<String>();
}

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

Очевидно, что использование интерфейса имеет много преимуществ (почему он там). В большинстве случаев не имеет значения, какая конкретная реализация используется библиотечной функцией. Но, возможно, есть случаи, когда это имеет значение. Например, если я знаю, что я буду в первую очередь обращаться к данным в списке случайным образом, LinkedList будет плохим. Но если моя функция библиотеки возвращает интерфейс, я просто не знаю. Чтобы быть в безопасности, мне даже может потребоваться скопировать список явно на ArrayList:

List bar = foo();
List myList = bar instanceof LinkedList ? new ArrayList(bar) : bar;

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

Что вы, ребята, думаете? Каковы ваши рекомендации, когда вы склонны к абстрактному решению, и когда вы раскрываете детали своей реализации для получения потенциальной выгоды?

4b9b3361

Ответ 1

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

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

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

Тем не менее, ваш пример воняет преждевременной оптимизации.

Если метод является или может быть критическим, он может упоминать детали реализации в документации.

Ответ 2

Верните соответствующий интерфейс, чтобы скрыть детали реализации. Ваши клиенты должны заботиться только о том, что предлагает ваш объект, а не о том, как вы его реализовали. Если вы начинаете с частного ArrayList и позже решаете, что что-то еще (например, LinkedLisk, список пропусков и т.д.) Более подходит, вы можете изменить реализацию, не затрагивая клиентов, если вы вернете интерфейс. В тот момент, когда вы возвращаете конкретный тип, возможность теряется.

Ответ 3

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

В этом контексте я бы ответил только на возвращение, что имеет смысл. Имеет ли смысл понимать, что возвращаемое значение является конкретным классом? Ака в вашем примере спросите себя: будет ли кто-нибудь использовать метод LinkedList для возвращаемого значения foo?

  • Если нет, просто используйте интерфейс более высокого уровня. Он намного более гибкий и позволяет вам изменять бэкэнд
  • Если да, спросите себя: не могу ли я реорганизовать свой код, чтобы вернуть интерфейс более высокого уровня?:)

Чем более абстрактным является ваш код, тем меньше изменений требуется сделать при смене бэкэнд. Это так просто.

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

В двух словах: code abstract, но явно:)

Ответ 4

Не имея возможности обосновать это с помощью конусов цитат из CS (я сам преподаю), я всегда придерживался мантры "Примите наименее производные, верните самые производные", когда разрабатываете классы, и он стоял я хорошо на протяжении многих лет.

Я предполагаю, что это означает, что с точки зрения интерфейса и конкретного результата мы понимаем, что если вы пытаетесь уменьшить зависимости и/или отделить, то возврат интерфейса обычно более полезен. Однако, если конкретный класс реализует больше, чем этот интерфейс, для вызывающих пользователей вашего метода обычно полезно получить конкретный класс назад (т.е. "Самый производный" ), а не ограничивать его подмножеством этой функции возвращаемого объекта - если вам действительно не нужно их ограничивать. Опять же, вы можете просто увеличить охват интерфейса. Никаких ограничений, подобных этому, я сравниваю с бездумным уплотнением классов; никогда не знаешь. Чтобы немного рассказать о первой части этой мантры (для других читателей), принятие наименее полученного также дает максимальную гибкость для вызывающих пользователей вашего метода.

-Oisin

Ответ 5

В общем, для открытого интерфейса, такого как API, возврат интерфейса (например, List) по конкретной реализации (например, ArrayList) будет лучше.

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

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

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

Ответ 6

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

Использование интерфейсов таким образом оказалось очень надежным способом записи многоразового кода.

Ответ 7

На главный вопрос уже дан ответ, и вы всегда должны использовать интерфейс. Я хотел бы просто прокомментировать

Очевидно, что использование интерфейса имеет много преимуществ (почему он там). В большинстве случаев не имеет значения, какая конкретная реализация используется библиотечной функцией. Но, возможно, есть случаи, когда это имеет значение. Например, если я знаю, что я буду в основном получать доступ к данным в списке случайным образом, LinkedList будет плохим. Но если моя функция библиотеки возвращает интерфейс, я просто не знаю. Чтобы быть в безопасности, мне даже может потребоваться скопировать список явно в ArrayList.

Если вы возвращаете структуру данных, которая, как вам известно, имеет плохую производительность произвольного доступа - O (n) и, как правило, LOT данных - есть другие интерфейсы, которые вы должны указывать вместо List, например Iterable, чтобы кто-либо, кто использует библиотека будет полностью знать, что доступен только последовательный доступ.

Выбор правильного типа для возврата - это не только реализация интерфейса или конкретной реализации, но и выбор правильного интерфейса.

Ответ 8

Не имеет значения, насколько API API возвращает интерфейс или конкретный класс; несмотря на то, что все здесь говорят, вы почти никогда не меняете класс реализации после написания кода.

Что гораздо важнее: всегда используйте интерфейсы минимального объема для параметров вашего метода! Таким образом, клиенты имеют максимальную свободу и могут использовать классы, о которых ваш код даже не знает.

Когда API-метод возвращает ArrayList, у меня нет абсолютно никаких проблем с этим, но когда он требует параметра ArrayList (или, для общего, Vector), я считаю, что охота на программиста и причинение ему вреда, потому что это означает, что я не могу использовать Arrays.asList(), Collections.singletonList() или Collections.EMPTY_LIST.

Ответ 9

Извините, что не согласен, но я думаю, что основное правило выглядит следующим образом:

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

Итак, в этом случае вы хотите объявить реализацию как:

public ArrayList<String> foo() {
  return new ArrayList<String>();
}

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

Пример 1: клиент хочет получить 5-й элемент:

  • return Collection: необходимо выполнить итерацию до 5-го элемента vs return List:
  • return List: list.get(4)

Пример 2: клиент хочет удалить 5-й элемент:

  • return List: должен создать новый список без указанного элемента (list.remove() не является обязательным).
  • return ArrayList: arrayList.remove(4)

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

Итак, снова правило может быть указано как:

  • Будьте гибкими в отношении того, что вы предлагаете.
  • Будьте информативны с тем, что вы доставляете.

Итак, в следующий раз, верните реализацию.

Ответ 10

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

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

Чтобы работать с интерфейсами, вы создадите их следующим образом:

IParser parser = new Parser();

Теперь IParser будет вашим интерфейсом, и Parser будет вашей реализацией. Теперь, когда вы работаете с объектом parser сверху, вы будете работать против интерфейса (IParser), который, в свою очередь, будет работать против вашей реализации (Parser).

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

Ответ 11

В общем случае использовать интерфейс во всех случаях, если вам не нужна функциональность конкретного класса. Обратите внимание, что для списков Java добавила класс маркеров RandomAccess, прежде всего, чтобы отличить обычный случай, когда алгоритм может понадобиться знать, если get ( i) постоянное время или нет.

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

Ответ 12

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

То, что вы делаете, - это программирование по контракту, хотя и ограниченным образом.

Это дает вам огромные возможности для изменения реализаций под обложками (при условии, что эти новые объекты соответствуют существующим контрактам/ожидаемым поведением).

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