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

Пример использования обобщений

Представим ситуацию, что заказчик дал нам работу в виде создания ПО для работы с продажей различных видов товаров, по различной цене.

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

Что делать в таком случае? Переписывать логику? Ведь если мы изменим тип данных, то вся программа сломается и будет куча ошибок из-за несоответствия типов в классе "продажи".

Выход из данной ситуации есть - и то это шаблоны!

Пример реализации

Давайте рассмотрим приведенный выше пример подробнее и реализуем его на практике

Реализация без шаблонов

Начнём с того, что мы не знаем, про обобщения и реализуем поставленную задачу без них.

Опишем поставленную задачу:

Программа имеет возможность продажи различных продуктов по заданной цене , например “хлеб” за 45 рублей. Исходя из этого можно понять, что мы имеем некий класс, назовем его Product , который имеет поля - Name (наименование продукта ), Price (цена продукта) и один метод Sell (метод продажи). (Рис.1)

Рис.1 Код класса Product
Рис.1 Код класса Product

Попробуем использовать написанный код

Создадим продукт , который будет являться хлебом , стоить он будет 45 рублей ( целое число), затем вызовем метод Sell() (Рис.2)

Рис.2 Код работы с классом Product
Рис.2 Код работы с классом Product

Запустим программу и увидим , что мы продали хлеб за 45 рублей. (Рис.3)

Рис.3 Вывод программы
Рис.3 Вывод программы

Проблема, которая может возникнуть

А теперь представим , что заказчик сообщил о том, что цену на хлеб необходимо указать с копейками.

У нас есть (пока что) лишь один выход из данной ситуации - изменить всю логику класса , задав тип данных вместо целого числа (int) - дробное (float). Действительно, если проделать данные действия , то заказчик будет удовлетворён , но что если логика продажи будет очень большой и нам из-за прихоти заказчика придется все переписывать? так как лично я достаточно ленивый человек - делать этого мне не очень будет хотеться.

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

Использование обобщений

Угловые скобки в описании class Product<T> указывают, что класс является обобщенным, а тип T, заключенный в угловые скобки, будет использоваться этим классом. Необязательно использовать именно букву T, это может быть и любая другая буква. Причем сейчас на этапе написания кода нам неизвестно, что это будет за тип, это может быть любой тип. Поэтому параметр T в угловых скобках еще называется универсальным параметром, так как вместо него можно подставить любой тип.

Изменим структуру нашего класса с использованием обобщения (Рис.4)

Рис.4 использование обобщения в классе Product
Рис.4 использование обобщения в классе Product

Теперь мы увидим ошибку в методе Main (Рис.5)

Рис.5 Ошибка
Рис.5 Ошибка

Данная ошибка появилась из-за того, что мы не используем int как было раньше, теперь мы используем обобщение с типом , следовательно нам необходимо, изменить объявление объекта класса Product, следующим образом (Рис.6)

Рис.6 Обновленный код
Рис.6 Обновленный код

В угловых скобках мы указали тип данных double , исходя из этого цена продукта T Price в классе Product, грубо говоря подставит тип данных double вместо T и в последующем будет его использовать.

Запустим программу и увидим, что мы продали товар за 45.5 рублей (Рис.7)

Рис.7 Вывод программы
Рис.7 Вывод программы

Теперь, мы можем для цены Price использовать любой тип данных для каждого нового объекта класса, например создадим новый объект и в качестве T укажем тип данных string

Создадим объект класса Product<string> назовем его milk и укажем название name - "Молоко", а цену Price - "Нет в наличии. 0 рублей", затем вызовем метод Sell (Рис.8)

Рис.8 новый объект класса
Рис.8 новый объект класса

Запустим программу и увидим, что все работает (Рис.9)

Рис.8 Запуск программы
Рис.8 Запуск программы

В рамках такого небольшого примера, трудно 'разойтись', но думаю в целом стало немного понятнее.

Также ярким примером использования шаблонов можно назвать всем известный класс List<T> , данный класс не знает с каким типом данных он будет работать заранее и при передаче в качестве любого типа данных, логика не поломается. А вот если бы класс шаблоны не использовал, то для каждого нового типа данных пришлось бы создавать копию класса с практически идентичной логикой, но просто переделанной под другой тип

Вывод

Обобщения присутствует во многих языках программирования. Шаблонизация используется фактически во всех динамических структурах языка C# (списки, очереди, стеки и т. д). Знание этого инструмента программирование поможет вам создавать более универсальные структуры и алгоритмы

Ссылки

Код примера можно найти тут

Канал на дзене

Комментарии (6)


  1. Naf2000
    20.11.2022 08:16
    +6

    Пример так себе. Вы делаете тип обобщённым и у вас сразу пропадает возможность использовать арифметические операторы. В новой версии, правда,появились обобщённые статические члены интерфейсов, но в любом случае надо описать ограничение на использование обобщения where.

    Но в примере удобнее сразу использовать decimal. А ещё лучше свой класс/структуру денежных сумм, со своим округлением.

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


  1. Mikluho
    20.11.2022 10:05
    +7

    Очень слабенькое описание, с указанием неправильной мотивации и неудачным примером..

    Первая ошибка в мотивации (причине) использования обобщений.

    generics необходимы в тех случаях, когда мы не можем заранее знать тип данных, который будем использовать.

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

    Вторая ошибка в самом примере. Всё же тип обобщения подразумевает контракт - цель его использования. Был бы у вас тип Product<TPrice> - было бы понятно, это тип цены, и это, очевидно, должен быть численный тип.

    Ну а дальше можно ещё больше покритиковать пример. Например, продукт сам себя не продаёт. В нормальном мире было бы что-то типа "market.sell(product, buyer);". А если уж надо по разному печатать цену в зависимости от условий, то у продукта был бы метод "ShowPrice()".

    Лучше бы вы приводили пример на базе библиотечных типов, заодно бы объяснили, чем хороши обобщения. Например, Dictionary<TKey, TValue> отлично раскрывает тему.


  1. fedorro
    20.11.2022 12:47
    +2

    Согласен с комментариями из обоих ваших статей - Вы не особо разбираетесь в том, о чем пишете. Рекомендую набраться опыта, а потом уже писать, если хочется, а не засорять Хабр не то что бесполезными, но даже вредными статьями.


  1. megazoid007
    20.11.2022 13:23

    С ними нужно быть аккуратным, легко не заметить и вернуться из ООП в процедурное программирование, все что можно решить через ООП (наследование и т.п.) лучше так и делать.


  1. xFFFF
    20.11.2022 13:23
    +3

    Пример очень слабенький. Кроме этого, деньги нельзя считать в double и float.


  1. JordanCpp
    20.11.2022 16:59
    +1

    Так себе пример. Деньги лучше инкапсулировать в value object. Расчёты с деньгами вполне нетривиальная штука с 100500 нюансами. Дженнреки или шаблоны, нужны в основном для алгоритмов и структур данных. Если их применять к чему то другому, получится извращение как описано в статье. Пример натягивания совы, на глобус.