Должен ли я использовать интерфейс, такой как IEnumerable, или конкретный класс, такой как List <> - PullRequest
4 голосов
/ 05 марта 2009

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

Допустим, мне нужно создать и передать контейнер в моей программе. У меня, вероятно, нет твердого мнения относительно одного вида контейнера по сравнению с другим, по крайней мере на данном этапе, но я выбираю один; в качестве аргумента, скажем, я собираюсь использовать List <> .

Вопрос в следующем: Лучше ли написать мои методы, чтобы принимать и возвращать интерфейс высокого уровня, такой как C #'s IEnumerable ? Или я должен написать методы для получения и передачи определенного класса контейнера, который я выбрал.

Какие факторы и критерии я должен искать, чтобы решить? Какие программы работают на пользу той или иной? Влияет ли язык компьютера на ваше решение? Спектакль? Размер программы? Личный стиль?

(это вообще имеет значение?)

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

Ответы [ 6 ]

16 голосов
/ 05 марта 2009

Ваш метод должен всегда принимать наименее конкретный тип, необходимый для выполнения своей функции. Если ваш метод должен перечислить, примите IEnumerable. Если он должен делать IList<> -конкретные вещи, по определению вы должны дать ему IList<>.

5 голосов
/ 05 марта 2009

Единственное, что должно повлиять на ваше решение, это то, как вы планируете использовать этот параметр. Если вы только перебираете его, используйте IEnumerable<T>. Если вы обращаетесь к проиндексированным членам (например, var x = list[3]) или каким-либо образом изменяете список (например, list.Add(x)), используйте ICollection<T> или IList<T>.

3 голосов
/ 05 марта 2009

Этот вопрос вызвал у меня дискуссию, поэтому Euro Micelli уже знает мой ответ, но вот он! :)

Я думаю, что Linq to Objects уже дает отличный ответ на этот вопрос. Используя самый простой интерфейс для последовательности элементов, он может обеспечить максимальную гибкость в отношении того, как реализовать эту последовательность, что позволяет создавать ленивые, повышая производительность без ущерба для производительности (ни в каком реальном смысле).

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

Есть те, кто скажет вам, что все данные в классе «проще» сделать общедоступными, на тот случай, если вам понадобится доступ к ним. Точно так же Евро посоветовал, что было бы лучше использовать богатый интерфейс для контейнера, такого как IList<T> (или даже конкретный класс List<T>), а затем вычистить беспорядок.

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

Итак, предположим, IEnumerable<T> будет представлять последовательность. Затем в тех случаях, когда вам нужно Add или Remove элементов (но при этом поиск по индексу по-прежнему не требуется), используйте IContainer<T>, который наследует IEnumerable<T> и, таким образом, будет идеально совместим с вашим другим кодом.

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

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

3 голосов
/ 05 марта 2009

Всегда есть компромисс. Общее эмпирическое правило - объявлять вещи как можно выше по иерархии. Так что если вам нужен только доступ к методам в IEnumerable, то это то, что вам следует использовать.

Другим недавним примером вопроса SO был API C, который взял имя файла вместо File * (или дескриптор файла). Там имя файла строго ограничивало, какие язвы могут передаваться (есть много вещей, которые вы можете передать с помощью дескриптора файла, но только то, что имеет имя файла).

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

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

0 голосов
/ 05 марта 2009

Я ответил на аналогичный вопрос C # здесь . Я думаю, что вы всегда должны предоставить самый простой контракт, который вы можете, который в случае коллекций, на мой взгляд, обычно IEnumerable Of T.

Реализация может быть предоставлена ​​внутренним типом BCL - будь то Set, Collection, List и т. Д. - чьи обязательные элементы предоставляются вашим типом.

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

0 голосов
/ 05 марта 2009

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

...