Обобщения (generics) необходимы в тех случаях, когда мы не можем заранее знать тип данных, который будем использовать. Они есть во многих языках программирования.
Пример использования обобщений
Представим ситуацию, что заказчик дал нам работу в виде создания ПО для работы с продажей различных видов товаров, по различной цене.
Все мы знаем (а может предстоит узнать), что заказчики очень изменчивые люди в плане своих проектов. Сегодня он скажет сделать цену в виде целого числа, а завтра ему понадобятся еще и копейки в виде дробной.
Что делать в таком случае? Переписывать логику? Ведь если мы изменим тип данных, то вся программа сломается и будет куча ошибок из-за несоответствия типов в классе "продажи".
Выход из данной ситуации есть - и то это шаблоны!
Пример реализации
Давайте рассмотрим приведенный выше пример подробнее и реализуем его на практике
Реализация без шаблонов
Начнём с того, что мы не знаем, про обобщения и реализуем поставленную задачу без них.
Опишем поставленную задачу:
Программа имеет возможность продажи различных продуктов по заданной цене , например “хлеб” за 45 рублей. Исходя из этого можно понять, что мы имеем некий класс, назовем его Product , который имеет поля - Name (наименование продукта ), Price (цена продукта) и один метод Sell (метод продажи). (Рис.1)
Попробуем использовать написанный код
Создадим продукт , который будет являться хлебом , стоить он будет 45 рублей ( целое число), затем вызовем метод Sell() (Рис.2)
Запустим программу и увидим , что мы продали хлеб за 45 рублей. (Рис.3)
Проблема, которая может возникнуть
А теперь представим , что заказчик сообщил о том, что цену на хлеб необходимо указать с копейками.
У нас есть (пока что) лишь один выход из данной ситуации - изменить всю логику класса , задав тип данных вместо целого числа (int) - дробное (float). Действительно, если проделать данные действия , то заказчик будет удовлетворён , но что если логика продажи будет очень большой и нам из-за прихоти заказчика придется все переписывать? так как лично я достаточно ленивый человек - делать этого мне не очень будет хотеться.
Теперь вот как раз таки обобщения выступают нам как «ангел хранитель», который поможет нам спасти себя от рутинной работы обновления логики каждый раз, когда заказчик будет менять свои запросы.
Использование обобщений
Угловые скобки в описании class Product<T> указывают, что класс является обобщенным, а тип T, заключенный в угловые скобки, будет использоваться этим классом. Необязательно использовать именно букву T, это может быть и любая другая буква. Причем сейчас на этапе написания кода нам неизвестно, что это будет за тип, это может быть любой тип. Поэтому параметр T в угловых скобках еще называется универсальным параметром, так как вместо него можно подставить любой тип.
Изменим структуру нашего класса с использованием обобщения (Рис.4)
Теперь мы увидим ошибку в методе Main (Рис.5)
Данная ошибка появилась из-за того, что мы не используем int как было раньше, теперь мы используем обобщение с типом T , следовательно нам необходимо, изменить объявление объекта класса Product, следующим образом (Рис.6)
В угловых скобках мы указали тип данных double , исходя из этого цена продукта T Price в классе Product, грубо говоря подставит тип данных double вместо T и в последующем будет его использовать.
Запустим программу и увидим, что мы продали товар за 45.5 рублей (Рис.7)
Теперь, мы можем для цены Price использовать любой тип данных для каждого нового объекта класса, например создадим новый объект и в качестве T укажем тип данных string
Создадим объект класса Product<string> назовем его milk и укажем название name - "Молоко", а цену Price - "Нет в наличии. 0 рублей", затем вызовем метод Sell (Рис.8)
Запустим программу и увидим, что все работает (Рис.9)
В рамках такого небольшого примера, трудно 'разойтись', но думаю в целом стало немного понятнее.
Также ярким примером использования шаблонов можно назвать всем известный класс List<T> , данный класс не знает с каким типом данных он будет работать заранее и при передаче в качестве T любого типа данных, логика не поломается. А вот если бы класс шаблоны не использовал, то для каждого нового типа данных пришлось бы создавать копию класса с практически идентичной логикой, но просто переделанной под другой тип
Вывод
Обобщения присутствует во многих языках программирования. Шаблонизация используется фактически во всех динамических структурах языка C# (списки, очереди, стеки и т. д). Знание этого инструмента программирование поможет вам создавать более универсальные структуры и алгоритмы
Ссылки
Комментарии (6)
Mikluho
20.11.2022 10:05+7Очень слабенькое описание, с указанием неправильной мотивации и неудачным примером..
Первая ошибка в мотивации (причине) использования обобщений.
generics необходимы в тех случаях, когда мы не можем заранее знать тип данных, который будем использовать.
На самом деле обобщения нужны тогда, когда мы хотим использовать код с множеством типов данных. В основном - для переиспользования логики.
Вторая ошибка в самом примере. Всё же тип обобщения подразумевает контракт - цель его использования. Был бы у вас тип Product<TPrice> - было бы понятно, это тип цены, и это, очевидно, должен быть численный тип.
Ну а дальше можно ещё больше покритиковать пример. Например, продукт сам себя не продаёт. В нормальном мире было бы что-то типа "market.sell(product, buyer);". А если уж надо по разному печатать цену в зависимости от условий, то у продукта был бы метод "ShowPrice()".
Лучше бы вы приводили пример на базе библиотечных типов, заодно бы объяснили, чем хороши обобщения. Например, Dictionary<TKey, TValue> отлично раскрывает тему.
fedorro
20.11.2022 12:47+2Согласен с комментариями из обоих ваших статей - Вы не особо разбираетесь в том, о чем пишете. Рекомендую набраться опыта, а потом уже писать, если хочется, а не засорять Хабр не то что бесполезными, но даже вредными статьями.
megazoid007
20.11.2022 13:23С ними нужно быть аккуратным, легко не заметить и вернуться из ООП в процедурное программирование, все что можно решить через ООП (наследование и т.п.) лучше так и делать.
xFFFF
20.11.2022 13:23+3Пример очень слабенький. Кроме этого, деньги нельзя считать в double и float.
JordanCpp
20.11.2022 16:59+1Так себе пример. Деньги лучше инкапсулировать в value object. Расчёты с деньгами вполне нетривиальная штука с 100500 нюансами. Дженнреки или шаблоны, нужны в основном для алгоритмов и структур данных. Если их применять к чему то другому, получится извращение как описано в статье. Пример натягивания совы, на глобус.
Naf2000
Пример так себе. Вы делаете тип обобщённым и у вас сразу пропадает возможность использовать арифметические операторы. В новой версии, правда,появились обобщённые статические члены интерфейсов, но в любом случае надо описать ограничение на использование обобщения where.
Но в примере удобнее сразу использовать decimal. А ещё лучше свой класс/структуру денежных сумм, со своим округлением.
Не стоит обобщать все возможные ситуации, часть из которых может и не произойти. Это как преждевременная оптимизация, только ещё хуже и в области архитектуры проекта.