У меня есть некоторые интерфейсы, которые я намерен внедрить сторонними разработчиками в будущем, и я сам предоставляю базовую реализацию. Я буду использовать только пару, чтобы показать пример.
В настоящее время они определены как
Вещь:
public interface Item {
String getId();
String getName();
}
ItemStack:
public interface ItemStackFactory {
ItemStack createItemStack(Item item, int quantity);
}
ItemStackContainer:
public interface ItemStackContainer {
default void add(ItemStack stack) {
add(stack, 1);
}
void add(ItemStack stack, int quantity);
}
Сейчас, Item
и ItemStackFactory
я могу совершенно предвидеть, что некоторые сторонние компании будут расширять его в будущем. ItemStackContainer
может также быть расширен в будущем, но не так, как я могу предвидеть, за пределами моей реализации по умолчанию.
Теперь я пытаюсь сделать эту библиотеку максимально надежной; это все еще находится на ранних стадиях (пре-пре-альфа), так что это может быть актом чрезмерной инженерии (YAGNI). Это подходящее место для использования дженериков?
public interface ItemStack<T extends Item> {
T getItem();
int getQuantity();
}
И
public interface ItemStackFactory<T extends ItemStack<I extends Item>> {
T createItemStack(I item, int quantity);
}
Я боюсь, что это может в конечном итоге сделать реализацию и использование более трудными для чтения и понимания; Я думаю, что это рекомендация избегать вложенных дженериков, где это возможно.
Ответы:
Вы используете дженерики в своем интерфейсе, когда ваша реализация, скорее всего, будет и дженериком.
Например, любая структура данных, которая может принимать произвольные объекты, является хорошим кандидатом на универсальный интерфейс. Примеры:
List<T>
аDictionary<K,V>
.Любая ситуация, когда вы хотите улучшить безопасность типов в обобщенном виде, является хорошим кандидатом на генерики. A
List<String>
- это список, который работает только со строками.Любая ситуация, в которой вы хотите применять SRP обобщенным образом и избегать методов, специфичных для типа, является хорошим кандидатом на обобщение. Например, PersonDocument, PlaceDocument и ThingDocument можно заменить на
Document<T>
.Шаблон Абстрактная фабрика - хороший пример использования универсального, тогда как обычный фабричный метод просто создает объекты, которые наследуются от общего, конкретного интерфейса.
источник
class StackFactory { Stack<T> Create<T>(T item, int count); }
. Я могу написать пример того, что я имел в виду под «уточнением параметра типа в подклассе» в ответе, если вам нужно больше разъяснений, хотя ответ будет только косвенно связан с исходным вопросом.Ваш план о том, как ввести общность в ваш интерфейс, кажется мне правильным. Однако ваш вопрос о том, будет ли это хорошей идеей или нет, требует несколько более сложного ответа.
За прошедшие годы я провел собеседование с несколькими кандидатами на должности разработчиков программного обеспечения от имени компаний, в которых я работал, и я понял, что большинство соискателей работы удобно использовать общие классы (например, стандартные классы коллекции), но не с написанием общих классов. Большинство в своей карьере никогда не писали ни одного общего класса и выглядят так, как если бы их попросили написать один. Поэтому, если вы навязываете использование обобщений любому, кто хочет реализовать ваш интерфейс или расширять базовые реализации, вы можете отталкивать людей.
Однако вы можете попытаться предоставить как универсальные, так и неуниверсальные версии ваших интерфейсов и базовых реализаций, чтобы люди, которые не делают дженерики, могли счастливо жить в своем узком мире, в котором много типов. сделать дженерики могут пожинать плоды более широкого понимания языка.
источник
Там ваше решение принято за вас. Если вы не предоставите генерики, они должны будут приводить к реализации
Item
каждый раз, когда они используют:Вы должны, по крайней мере, сделать
ItemStack
общий способ, как вы предлагаете. Самое глубокое преимущество дженериков в том, что вам почти никогда не нужно ничего разыгрывать, а предложение кода, который вынуждает пользователей разыгрывать, является ИМХО уголовным преступлением.источник
Ух ты ... У меня совершенно другой взгляд на это.
Это ошибка. Вы не должны писать код «на будущее».
Также интерфейсы написаны сверху вниз. Вы не смотрите на класс и не говорите: «Эй, этот класс может полностью реализовать интерфейс, и тогда я смогу использовать этот интерфейс где-нибудь еще». Это неправильный подход, потому что только класс, который использует интерфейс, действительно знает, каким должен быть интерфейс. Вместо этого вы должны подумать: «В этом месте моему классу нужна зависимость. Позвольте мне определить интерфейс для этой зависимости. Когда я закончу писать этот класс, я напишу реализацию этой зависимости». Будет ли кто-то когда-либо повторно использовать ваш интерфейс и заменять вашу реализацию по умолчанию своей собственной, совершенно не имеет значения. Они могут никогда не делать. Это не значит, что интерфейс был бесполезен. Это заставляет ваш код следовать принципу Inversion of Control, что делает ваш код очень расширяемым во многих местах "
источник
Я думаю, что использование дженериков в вашей ситуации слишком сложно.
Если вы выбираете непатентованную версию интерфейсов, дизайн указывает, что
Эти неявные предположения и ограничения являются очень важными вещами, которые необходимо тщательно продумать. Поэтому, особенно на ранней стадии, эти преждевременные проекты не должны быть введены. Просто, легко понять, желателен общий дизайн.
источник
Я бы сказал, что это зависит главным образом от этого вопроса: если кому-то нужно расширить функциональность
Item
иItemStackFactory
,В первом случае нет необходимости в дженериках, во втором, вероятно, есть.
источник
Возможно, у вас может быть иерархия интерфейса, где Foo - это один интерфейс, а Bar - другой. Всякий раз, когда тип должен быть и тем и другим, это FooAndBar.
Чтобы избежать чрезмерного описания, используйте тип FooAndBar только тогда, когда это необходимо, и сохраняйте список интерфейсов, которые никогда ничего не расширят.
Это намного удобнее для большинства программистов (большинство из них любят проверку типов или предпочитают никогда не иметь дело со статической типизацией). Очень немногие люди написали свой собственный класс дженериков.
Также как примечание: интерфейсы о том, какие возможные действия могут быть выполнены. На самом деле они должны быть глаголами, а не существительными.
источник