В чем выгода немедленного понижения? - PullRequest
6 голосов
/ 09 марта 2012

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

У меня действительно есть несколько примеров, но вот тот, который я вытащил прямо из кода:

public Set<Coordinates> neighboringCoordinates() {
    HashSet<Coordinates> neighbors = new HashSet<Coordinates>();
    neighbors.add(getNorthWest());
    neighbors.add(getNorth());
    neighbors.add(getNorthEast());
    neighbors.add(getWest());
    neighbors.add(getEast());
    neighbors.add(getSouthEast());
    neighbors.add(getSouth());
    neighbors.add(getSouthWest());
    return neighbors;
}

Ииз того же проекта, вот еще один (возможно, более краткий) пример:

private Set<Coordinates> liveCellCoordinates = new HashSet<Coordinates>();

В первом примере вы можете видеть, что метод имеет тип возвращаемого значения Set<Coordinates> - однако этот конкретный метод всегда будеттолько вернуть HashSet - и никакой другой тип Set.

Во втором примере liveCellCoordinates первоначально определяется как Set<Coordinates>, но сразу же превращается в HashSet.

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

Мне интересно, какая логика стоит за этим?Существуют ли некоторые соглашения по кодексу, которые бы учитывали эту хорошую практику?Делает ли это программу быстрее или эффективнее?Какая польза от этого?

Ответы [ 8 ]

12 голосов
/ 09 марта 2012

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

Реалистичным примером было бы, если бы было решено, что neighboringCoordinates() необходимо вернуть потокобезопасный Set объект. Как написано, это было бы очень просто сделать - замените последнюю строку метода на:

return Collections.synchronizedSet(neighbors);

Как оказалось, объект Set, возвращаемый synchronizedSet(), не совместим с присваиванием с HashSet. Хорошо, что метод был объявлен для возврата Set!

Аналогичное соображение применимо ко второму случаю. Код в классе, который использует liveCellCoordinates, не должен знать ничего больше, чем Set. (На самом деле, в первом примере я ожидал увидеть:

Set<Coordinates> neighbors = new HashSet<Coordinates>();

в начале метода.)

4 голосов
/ 09 марта 2012

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

Давай у тебя было:

HashedSet<Coordinates> c = neighboringCoordinates()

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

Но если у вас есть:

Set<Coordinates> c = neighboringCoordinates()

Пока в их коллекции реализован набор, они могут изменять все, что хотят, внутренне, не затрагивая ваш код.

По сути, это просто наименее конкретная возможность (в пределах разумного) ради сокрытия внутренних деталей. Ваш код заботится только о том, чтобы он мог получить доступ к коллекции в виде набора. Неважно, какой это конкретный тип набора, если это имеет смысл. Итак, зачем делать ваш код связанным с HashedSet?

2 голосов
/ 09 марта 2012

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

Например, допустим, вы реализовали метод, подобный этому: List<User> users = getUsers();, хотя на самом деле вы могли бы использовать более абстрактный тип, подобный этому: Collection<User> users = getUsers();. Теперь Боб может ошибочно предположить, что ваш метод возвращает пользователей в алфавитном порядке и создать ошибку. Если бы вы использовали Collection, не было бы такой путаницы.

2 голосов
/ 09 марта 2012

Вопрос в том, как вы хотите использовать переменную. например это в вашем контексте важно , что это HashSet? Если нет, вы должны сказать, что вам нужно, и это просто Set.

Все было бы иначе, если бы вы использовали, например, TreeSet здесь. Тогда вы потеряете информацию о том, что Set отсортирован, и если ваш алгоритм использует это свойство, изменение реализации на HashSet станет катастрофой. В этом случае лучшим решением было бы написать SortedSet<Coordinates> set = new TreeSet<Coordinates>();. Или представьте, что вы написали бы List<String> list = new LinkedList<String>();: Это нормально, если вы хотите использовать list просто как список, но вы не сможете больше использовать LinkedList в качестве deque, как такие методы, как offerFirst или peekLast не на List интерфейсе.

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

2 голосов
/ 09 марта 2012

Принцип дизайна в игре: «Всегда предпочитайте указывать абстрактные типы».

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

Вы должны сделать это с полямиа также, например:

private List<String> names = new ArrayList<String>;

not

private ArrayList<String> names = new ArrayList<String>;

Позже вы можете захотеть перейти на использование LinkedList - указание абстрактного типа позволяет делать это без кодаизменения (кроме инициализации курса).

2 голосов
/ 09 марта 2012

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

1 голос
/ 09 марта 2012

Хитрость в том, что " код для интерфейса ".

Причина этого заключается в том, что в 99,9% случаев вы просто хотите, чтобы поведение HashSet/TreeSet/WhateverSet соответствовало Set -интерфейсу, реализованному всеми ими. Это упрощает ваш код, и единственная причина, по которой вам действительно нужно сказать HashSet, - указать, какое поведение имеет набор, который вам нужен.

Как вы, возможно, знаете, HashSet относительно быстр, но возвращает элементы в, казалось бы, случайном порядке. TreeSet немного медленнее, но возвращает элементы в алфавитном порядке. Вашему коду все равно, пока он ведет себя как Set.

Это дает более простой код, с которым легче работать.

Обратите внимание, что типичным выбором для Set является HashSet, Map - HashMap, а List - ArrayList. Если вы используете нестандартную (для вас) реализацию, для этого должна быть веская причина (например, необходима сортировка по алфавиту), и эту причину следует указать в комментарии рядом с оператором new. Облегчает жизнь будущим сопровождающим.

1 голос
/ 09 марта 2012

Все довольно просто.

В вашем примере метод возвращает Set. С точки зрения разработчика API это имеет одно существенное преимущество по сравнению с возвратом HashSet.

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

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