Должен ли возвращаемый тип объявления метода быть интерфейсом или конкретным классом?
В общем случае интерфейс или абстрактный класс часто является подходящим решением, я прав?
Но в некоторых случаях кажется, что конкретный класс лучше. Например,
public string Replace(string old, string new)
Replace
метод String
возвращает конкретный класс. (Это всего лишь пример, хотя String не реализует никаких интерфейсов.)
Мой вопрос
Когда я должен вернуть интерфейс, и когда я должен вернуть конкретный класс?
Является ли это частью
program to an interface, not an implementation
для возврата интерфейса?
4 ответа
Это зависит.
Я видел, как этот вопрос задавали пару раз, и вот хороший пример, иллюстрирующий ответ "все зависит".
Рассмотрим следующий класс:
public class MyClass
{
public static IEnumerable<int> Test()
{
return new List<int> { 2, 3, 4 };
}
public static List<int> Test2()
{
return new List<int> { 2, 3, 4 };
}
}
Test
возвращает IEnumerable
а также Test2
возвращает конкретную реализацию IEnumerable
интерфейс (List
в таком случае). Какой метод самый лучший? Test
или же Test2
?
На самом деле, оба семантически разные:
- Как
Test
только возвращаетIEnumerable
, это подразумевает, что это часть контракта метода, что разработчик использует возвращенный объект в перечислении (foreach
). - Как
Test2
возвращаетList
Например, он позволяет пользователю получить доступ к объектамList
по индексу Это совершенно другое использование возвращаемого объекта.
private static void Main(string[] args)
{
foreach (var z in MyClass.Test())
{
Console.WriteLine(z);
}
var f = MyClass.Test2()[0];
Console.ReadKey();
}
Если вы ожидаете, что разработчик будет использовать возвращенный объект только в перечислении, то вы можете использовать интерфейс в качестве возвращаемого типа. Если вы ожидаете, что разработчик будет использовать методы / свойства конкретной реализации интерфейса (в приведенном выше примере доступ к объекту по индексу), тогда вы можете вернуть конкретный тип.
Также помните, что иногда у вас нет выбора. Например, если вы хотите предоставить общедоступную коллекцию, которая должна использоваться для привязки Silverlight, вы должны вернуть ObservableCollection<T>
не IEnumerable<T>
потому что система привязки действительно нуждается в методе / свойствах / поведении ObservableCollection
учебный класс (IEnumerable
было бы недостаточно для привязки к работе).
Чего следует избегать, так это метод, который возвращает IEnumerable<T>
и это используется с ToList()
каждый раз.
По моему мнению, это зависит, скажем, если у вас есть метод, который используется в тесно связанном виде, то есть класс 1 вызывает метод A в классе 2 и всегда хочет определенный тип и это единственный раз, когда метод вызывается, тогда вы могли бы поспорить там нет смысла. Примером этого может быть возвращение IEnumerable, где метод создает коллекцию в виде списка, а затем возвращает его как IEnumerable, а класс 1 все равно использует его как список, поэтому для возврата в любом случае потребуется вызов ToList().
Однако, когда я пишу интерфейс между классами, такой как слой данных, я делаю более разделенный интерфейс, я всегда предпочитаю возвращать наименьший общий знаменатель (например, IEnumarable) и разрешать потребителю, который может не знать, что с ним делать.
Вы получаете Program to an interface, not an implementation
неправильно я верю. Что GOF подразумевает под программой для интерфейса, является интерфейсом типа.
Они определяют интерфейс объекта как все методы, которые видны снаружи. Таким образом, их определение интерфейса в этом случае отличается от того, которое у вас есть, например, в C#.
Что касается вашего вопроса, я думаю, что лучше вернуть абстрактный класс. Будет ли это работать, если вы вернете какой-либо объект, полученный из этого абстрактного класса? Если да, то вернуть абстрактный класс. Если нет, перейдите более конкретно и верните производный тип.
Зависит от того, сколько доступа вы хотите предоставить пользователю вашего кода!
Я имею в виду, возвращаясь List<AccountStatement>
для пользователя не будет никакого смысла. Потому что вы не хотели бы, чтобы пользователь вашего кода ДОБАВЛЯЛ новый оператор в этой коллекции (оператор должен быть создан при бронировании конкретной транзакции)
Итак, в этом случае вы можете просто вернуться IEnumerable<AccountStatement>
доступ только для чтения.
В целом, я поддерживаю идею возврата интерфейсов из методов по следующим причинам:
- Вы получаете лучший контроль над доступом, который вы даете своему объекту к внешнему миру.
- Вы можете скрыть детали реализации (в этом случае даже ваш класс может оставаться внутренним для сборки)
- Он соответствует принципу сегрегации интерфейса (раскрывайте желаемое поведение, только возвращая и реализуя правильный интерфейс).
Кстати, ваш пример возврата "строки" не очень полезен. Это примитивный тип данных (я имею в виду собственный тип данных в mscorlib), и скрывать / показывать в этом объекте нечего.