Ковариантный тип возвращаемых данных лучшие практики - PullRequest
0 голосов
/ 19 февраля 2019

Я смутно помню, как узнал в университете, что тип возвращаемого значения метода всегда должен быть как можно более узким, но мой поиск любых ссылок в Интернете оказался пустым, и SonarQube называет это запахом кода.Например, в следующем примере (обратите внимание, что TreeSet и Set являются просто примерами)

public interface NumberGetter {
    Number get();

    Set<Number> getAll();
}

public class IntegerGetter implements NumberGetter {

    @Override
    public Integer get() {
        return 1;
    }

    @Override
    public TreeSet<Number> getAll() {
        return new TreeSet<>(Collections.singleton(1));
    }
}

SonarQube говорит мне

Объявления должны использовать интерфейсы коллекции Java, такие как «Список»", а не конкретные классы реализации, такие как" LinkedList ".Цель API Коллекций Java - предоставить четко определенную иерархию интерфейсов, чтобы скрыть детали реализации.(squid: S1319)

Я вижу смысл скрывать детали реализации, так как не могу легко изменить тип возвращаемого значения IntegerGetter :: getAll () без нарушения обратной совместимости.Но, делая это, я также предоставляю потребителям потенциально ценную информацию, то есть они могут изменить свой алгоритм, чтобы он был более подходящим для использования TreeSet.И если они не заботятся об этом свойстве, они все равно могут просто использовать IntegerGetter (как бы они его не получили), например:

Set<Number> allNumbers = integerGetter.getAll();

Итак, у меня есть следующие вопросы:

  • Уместно ли для IntegerGetter :: get () возвращать более узкий тип?
  • Уместно ли для IntegerGetter :: getAll () возвращать более узкий тип?
  • Есть ликакие-либо передовые практики по этой теме, или ответ только "все зависит"?

(NB SonarQube не жалуется, если я заменю TreeSet, например, SortedSet. Этот код пахнет только оне используется интерфейс API коллекции? Что делать, если для моей конкретной задачи существует только конкретный класс?)

Ответы [ 3 ]

0 голосов
/ 19 февраля 2019

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

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

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

В случае IntegerGetter.getAll() это зависит от того, для чего вызывающий использует метод:

  • Если он просто хочет выполнить итерацию, Iterable будет правильным выбором.,
  • Если нам нужно больше методов, таких как размер, Collection может.
  • Если он полагается на уникальность чисел, a Set.
  • Если он дополнительно полагается на сортируемые числа, a SortedSet.
  • И если на самом деле ему нужно быть красно-черным деревом из JDK, чтобы он мог напрямую манипулировать своим внутренним состоянием для отвратительного взлома, TreeSet может быть правильным выбором.
0 голосов
/ 19 февраля 2019

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

Я дам вам более общий ответ.

Хотите ли вы, чтобы ваш потребитель имел только петли?Вернуть Collection<T>.
Хотите ли вы, чтобы ваш потребитель мог получить доступ по индексу?Возврат List<T>
Хотите, чтобы ваш потребитель знал и мог эффективно проверить наличие элемента?Возврат Set<T>
и т. Д.

Тот же подход действителен для входных параметров.Какой смысл принимать List<T> или даже конкретные классы, такие как ArrayList<T> или LinkedList<T>, если вы только зациклите его?Вы просто предоставляете своему коду меньше гибкости для будущих модификаций.

То, что вы делаете здесь с возвращаемыми типами IntegerGetter наследуемых методов, называется специализация типа .Это нормально, если вы продолжаете выставлять интерфейсы внешнему миру.

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

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

0 голосов
/ 19 февраля 2019

Я стараюсь, как правило, использовать в сигнатуре метода самый общий тип (класс или интерфейс не имеет значения), который поддерживает необходимый мне API: более общий тип , более минимальный API.Итак, если мне нужен параметр, представляющий семейство объектов того же типа, я начинаю использовать Collection;если в конкретном сценарии важна идея упорядочения , я использую List, избегая публикации какой-либо информации о конкретной реализации List, которую я использую внутри: моя идея состоит в том, чтобы сохранить возможность изменять реализацию(может быть для оптимизации производительности, для поддержки другой структуры данных и т. д.) без перерыва клиентов.

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

Итак, отвечая на ваши вопросы:

  1. да, в реализации IntegerGetter интерфейса NumberGetter уместно возвращать более узкий тип: Java позволяет вам делатьи вы не нарушаете мое правило более общее - более красивое : интерфейс NumberGetter предоставляет более общий интерфейс, используя Number в качестве ретуТип rn, но в конкретных реализациях мы можем использовать более узкий тип возвращаемого значения для руководства реализацией метода: этот выбор не влияет на клиентов, ссылающихся на более абстрактный интерфейс, и клиент, ссылающийся на конкретный подкласс, может использовать преимущество более конкретного интерфейса
  2. то же, что и в предыдущем пункте, но я думаю, что это менее полезно, чем в предыдущем случае для клиентов: может быть, клиент найдет полезным ссылаться на Integer, чем на Number (если я явно использую NumberGetter, может быть, я думаю, с точки зрения Integer с, а не с Number с), но обращение к TreeSet вместо Set полезно, только если вам нужен API, предоставляемый подклассом ине по интерфейсу ...
  3. см. первоначальную диссертацию

Это квазифилософский вопрос - и мой ответ таков: надеюсь, он вам пригодится!

...