Обобщения (generics)¶
Ключевой причиной использования обобщений является предоставление выразительных ограничителей типов используемых в нескольких частях кода. Частями могут быть:
- члены экземпляра класса
- методы класса
- параметры функции
- возвращаемое значение функции
Мотивация и примеры¶
Рассмотрим простую реализацию структуры данных Очередь
(первым пришел, первым вышел). Простая в TypeScript / JavaScript выглядит так:
1 2 3 4 5 |
|
Одной из проблем этой реализации является то, что она позволяет людям добавлять что угодно в очередь, а когда они это удаляют - это тоже может быть что угодно. Это показано ниже, где кто-то может добавить строку
в очередь, в то время как использование фактически предполагает, чтобы были добавлены только числа
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Одно из решений (и на самом деле единственное в языках, которые не поддерживают обобщения) состоит в том, чтобы пойти дальше и создать специальные классы с этими ограничениями. Например очередь номеров по-быстрому:
1 2 3 4 5 6 7 8 9 10 11 |
|
Конечно, это может быстро превратится в боль, например, если вам нужна очередь строк, вам придется написать всё заново. На самом деле вы просто хотите чтобы независимо от того, какой тип элемента добавляется, он должен быть одинаковым для всего, что удаляется. Это легко сделать с помощью параметра обобщение (в данном случае на уровне класса):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Другой пример, который мы уже видели - это функция reverse, здесь есть общий ограничитель на то, что передается в функцию, и то, что возвращает функция:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
В этом разделе вы видели примеры определения обобщений на уровне класса и на уровне функций. Незначительное дополнение: вы можете создавать обобщения только для методов класса. В качестве мини-примера рассмотрим следующий, где мы перемещаем функцию «reverse» в класс «Utility»:
1 2 3 4 5 6 7 8 9 |
|
СОВЕТ: Вы можете называть параметр обобщения как хотите. Обычно используется
T
,U
,V
, когда у вас есть простые обобщения. Если у вас более одного параметра попробуйте использовать значимые имена, например,TKey
иTValue
(обычно обобщения с префиксомT
также называют шаблонами на других языках, например C ++).
Бесполезные обобщения¶
Я видел, как люди используют обобщения просто так. Вопрос, который нужно задать, это какое ограничение вы пытаетесь описать. Если вы не можете ответить на него легко, у вас может быть бесполезное обобщение. Например. следующая функция
1 |
|
Здесь обобщение T
совершенно бесполезно, поскольку оно используется только в единственном месте - в параметре. Это могло бы быть просто:
1 |
|
Шаблон проектирования: целесообразные обобщения¶
Рассмотрим функцию:
1 |
|
В этом случае вы можете видеть, что тип T
используется только в одном месте. Таким образом, нет никаких ограничителей переиспользуемых между частями функции или класса. Это эквивалентно утверждению типа с точки зрения проверок:
1 2 3 |
|
Обобщения, используемые только один раз, не лучше, чем утверждение с точки зрения надежности проверки типов. Тем не менее, они обеспечивают удобство для вашего API.
Более очевидный пример - функция, которая загружает ответ json. Она возвращает промис любого типа, который вы передадите:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Обратите внимание, что вам все равно нужно явно описывать то, что вы хотите, но сигнатура getJSON<T>
(config) => Promise<T>
сохраняет вам несколько нажатий клавиш (вам не придётся описывать возвращаемый тип loadUsers
):
1 2 3 4 5 6 7 8 9 10 11 |
|
Также Promise<T>
в качестве возвращаемого значения определенно лучше, чем альтернативы, такие как Promise<any>
.