Generics
Generics
Основна мотивація для дженериків — документувати значущі залежності типів між членами. Членами можуть бути:
Члени примірника класу
Методи класу
аргументи функції
значення, що повертається функцією
Motivation and samples
Розглянемо просту реалізацію структури даних Queue
(першим прийшов, першим вийшов). Приклад у TypeScript / JavaScript виглядає так:
Одна проблема з цією реалізацією полягає в тому, що вона дозволяє людям додавати будь-що до черги, а коли вони відкривають її, це може бути нічого. Це показано нижче, де хтось може вставити string
в чергу, тоді як використання фактично передбачає, що було вставлено лише numbers
:
Одне з рішень (і фактично єдине в мовах, які не підтримують універсали(generics)) полягає в тому, щоб створити special класи тільки для цих обмежень. Наприклад швидка черга номерів:
Звичайно, це може швидко стати болісним, напр. якщо вам потрібна черга рядків, вам доведеться знову пройти через усі ці зусилля. Те, що вам дійсно потрібно, це спосіб сказати, що незалежно від типу матеріалу, який додаеться він повинен бути таким самим, як видається. Це легко зробити за допомогою параметра generic (у цьому випадку на рівні класу):
Ще один приклад, який ми вже бачили, це функція reverse, тут обмеження між тим, що передається у функцію, і тим, що функція повертає:
У цьому розділі ви бачили приклади генериків, визначених at class level та на function level. Одне невелике доповнення, яке варто згадати, полягає в тому, що ви можете створювати генерики лише для функції-члена. Як приклад розглянемо наступне, де ми переміщуємо функцію reverse
в клас Utility
:
ПОРАДА: Ви можете називати загальний параметр як завгодно. Традиційно використовують
T
,U
абоV
, коли у вас є прості генерики. Якщо у вас є більше ніж один загальний аргумент, спробуйте використовувати значущі імена, як-отTKey
іTValue
. Конвенція передбачає префіксT
, оскільки генерики також називаються templates в інших мовах, наприклад C++.
Design Pattern: Convenience generic
Розглянемо функцію:
У цьому випадку ви можете побачити, що тип T
використовується лише в одному місці. Тому немає обмежень between членами. Це еквівалентно твердженню типу з точки зору безпеки типу:
Дженерики, використані only once не кращі за твердження з точки зору безпеки типу. Тим не менш, вони забезпечують convenience для вашого API.
Більш очевидним прикладом є функція, яка завантажує відповідь json. Він повертає обіцянку whatever type you pass in:
Зауважте, що вам все одно потрібно явно анотувати те, що ви хочете, але підпис getJSON<T>
(config) => Promise<T>
заощаджує вам кілька натискань клавіш (вам не потрібно коментувати тип повернення loadUsers
, як це можна зробити висновок):
Крім того, Promise<T>
як значення, що повертається, однозначно краще, ніж альтернативи, такі як Promise<any>
.
Іншим прикладом є те, що загальне слово використовується лише як аргумент:
Тут загальний T
можна використовувати для позначення типу, якому ви хочете, щоб аргумент відповідав, наприклад.
Last updated