Перейти к содержанию
  • Категории
  • Последние
  • Метки
  • Популярные
  • Пользователи
  • Группы
Свернуть
Логотип бренда
Категории
  1. Главная
  2. Категории
  3. Языки программирования
  4. TypeScript
  5. Тайная техника Хокаге: Полиморфизм и Дженерики в TypeScript

Тайная техника Хокаге: Полиморфизм и Дженерики в TypeScript

Запланировано Прикреплена Закрыта Перенесена TypeScript
typescript
1 Сообщения 1 Постеры 62 Просмотры 1 Watching
  • Сначала старые
  • Сначала новые
  • По количеству голосов
Ответить
  • Ответить, создав новую тему
Авторизуйтесь, чтобы ответить
Эта тема была удалена. Только пользователи с правом управления темами могут её видеть.
  • kirilljsK Не в сети
    kirilljsK Не в сети
    kirilljs
    js
    написал в отредактировано kirilljs
    #1

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

    Поехалииииииии

    Дженерики и полиморфизм являются связанными концепциями в программировании, особенно в контексте объектно-ориентированного программирования (ООП) и языков, поддерживающих статическую типизацию, таких как TypeScript. Однако они не являются одним и тем же понятием; каждый из них имеет свои особенности и применения.

    Дженерики

    Дженерики — это механизм, позволяющий писать код, который может работать с различными типами данных без потери типовой безопасности. Они используются для создания универсальных компонентов, таких как коллекции, функции и классы, которые могут работать с любыми типами данных, сохраняя при этом строгую проверку типов.

    function identity<T>(arg: T): T {
        return arg;
    }
    

    Здесь T является переменной типа параметра, которая позволяет функции identity принимать аргументы любого типа и возвращать значение того же типа.

    Полиморфизм

    Полиморфизм — это способность системы использовать объекты разных типов, но с похожими интерфейсами, так, чтобы эти объекты могли взаимодействовать друг с другом без необходимости изменения их внутренней реализации. Полиморфизм позволяет программисту писать более гибкий и модульный код, поскольку различные классы могут реализовывать один и тот же интерфейс по-разному.

    class Animal {
        speak() {
            console.log('Это животинка');
        }
    }
    
    class Pig extends Animal {
        speak() {
            console.log('Хрюшка делает: хрю хрю');
        }
    }
    

    Здесь Animal является базовым классом с методом speak, а Pig является подклассом, который переопределяет этот метод, предоставляя свою уникальную реализацию.

    Ну а теперь о главном

    Что такое конкретные типы в TypeScript - это:

    // Пример если что
    boolean;
    string;
    Date[];
    {a: number} | {b: string};
    (number: number[]) => number;
    

    Конкретные типы полезны, но полезны когда Вы точно знаете какой ожидается тип и хотите сверить его с переданным типом. Но иногда это нецелесообразно ограничивать поведение функций конкретным типом.

    Давай представим с Вами функцию filter для итерации по массиву и его очистки.
    В JS она может выглядеть примерно так:

    function filter(array, f) {
      let result = []
      for (let i = 0; i < array.length; i++) {
        let item = array[i]
        if(f(item)) {
          result.push(item)
        }
      }
    }
    
    filter([1, 2, 3, 4], _ => _ <= 3) // вычисляется как [1, 2]
    

    А теперь давайте приступим с извлечения сигнатуры типа filter и добавления временных заместителей unknown для типов:

    type Filter = {
      (array: unknown, f: unknown) => unknown[]
    }
    

    Далее попробуем заполнить типы, к примеру возьмем number:

    type Filter = {
      (array: number[], f: (item: number) => boolean): number[]
    }
    

    Такая сигнатура будет работать для массива чисел, но не как не строк и уже тем более не объектов и других массивов. Давайте теперь попробуем использовать перегрузку для ее расширения:

    type Filter = {
      (array: number[], f: (item: number) => boolean): number[]
      (array: string[], f: (item: string) => boolean): string[]
    }
    

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

    type Filter = {
      (array: number[], f: (item: number) => boolean): number[]
      (array: string[], f: (item: string) => boolean): string[]
      (array: object[], f: (item: object) => boolean): object[]
    }
    

    В принципе выглядит неплохо, но если реализовать функцию filter с сигнатурой filter: Filter и ее использовать, то получим следующее:

    let names = [
      {firstName: 'Lox'},
      {firstName: 'Debik'},
      {firstName: 'Dodik'},
    ]
    
    let result = filter(
      names,
      _ => _.firstName.startsWith('L')
    ) // Ошибка TS2339: свойство 'firstName' не существует в типе 'object'.
    
    result[0].firstName // Ошибка TS2339: свойство 'firstName'
                        //не существует в типе 'object'.
    

    В этом месте должно стать понятно, почему TypeScript выдает ошибку.
    Мы сообщили ему, что можем передать массив чисел, строк или объектов в filter, и передали массив объектов. Но как Вы уже должны знать, object ничего не сообщает TypeScript о конкретной форме самого объекта.

    И что же делать спросите вы?

    Если ранее вы писали на языке, который поддерживает обобщенные типы, то наверняка хрюкните “ХОЧУ ОБОБЩЕННЫЕ ТИПЫ”!
    Замечательная новость в том, что вы правы, а вот плохая - вы только что разбудили @Jspi

    Для несведущих начну с определения обобщенных типов, а затем приведу пример с нашей функцией.

    ПАРАМЕТР ОБОБЩЕННОГО ТИПА

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

    Едем дальше. Вот как будет выглядеть тип filter, если мы перепишем его с параметром обобщенного типа T:

    type Filter = {
      <T>(array: T[], f: (item: T) => boolean): T[]
    }
    

    Таким образом мы сообщили: “Функция filter использует параметр обобщенного типа T. Мы заранее не знаем, каким будет тот или ной тип в дальнейшем, поэтому, TypeScript, если ты сможешь сделать его вывод при каждом вызове filter, то было бы замечательно”.
    TypeScript выводит тип T на основе типа, который мы передаем для array. Как только TypeScript делает вывод, чем является T для вызова filter, он подставляет этот тип для каждого видимого T.
    T выступает в роли замещающего типа, который заполняется модулем проверки на основе контекста. Он параметризует тип Filter, поэтому мы и зовем его параметром обобщенного типа.

    “Фраза параметр обобщенного типа часто заменяется на обобщенный тип или обобщение.”

    Вообще для объявления обобщенных типов используются угловые скобки (<>) А еще их называют ДЖЕНЕРИКАМИ (Generics), воспринимайте их как ключевое слово type.
    Место размещения скобок определяет диапазон охватываемых типов (да, кстати есть всего несколько мест где вы можете их использовать). TypeScript в свою очередь убеждается, что внутри этого диапазона все экземпляры параметров обобщенных типов привязаны к одному реальному типу. В текущем примере при вызове filter TypeScript привяжет конкретные типы к обобщенному типу T в зависимости от обозначенного скобками диапазона. Какой именно тип привязывать к T, он решит исходя из того, с каким типом мы вызовем filter. Между угловых скобок мы можем объявить столько обобщений, сколько пожелаем, разделив их точкой с запятой.

    T - это просто имя типа, такое же как А, LOX, baloven, 2k24. Но в мире TS принято использовать имена состоящие из одной заглавной буквы, так что всего скорее в чужом коде вы встретите такие имена типов как T, U, V, W и т.п.

    Если конечно вы используете множество типов или какие-то сложные образы то конечно лучше стоит рассмотреть вариант вроде Value или WidgetType.

    А еще некоторые программисты предпочитают начинать с А вместо Т. Это зависит от сообщества пользователей разных языков программирования которые делают выбор согласно устоявшейся традиции. Например функциональщики предпочитают A, B, C. Разработчики ООП склонны использовать Т или type. TypeScript поддерживает любой стиль, но предпочтительнее используется последний

    ПРОДОЛЖАЕМ!
    Подобно тому как параметр функции повторно привязывается при каждому вызове функции, также и каждый вызов filter получает свою привязку для T:

    type Filter = {
      <T>(array: T[], f: (item: T) => boolean): T[]
    }
    
    let filter: Filter = (array, f) => // ...
    
    // (a) T привязан к number
    filter([1,2,3], _=>_>2)
    
    // (b) T привязан к строке
    filter(['a', 'b'], _=>_!=='b')
    
    // (c) T привязан к {firstName: string}
    let names = [
      {firstName: 'lox'},
      {firstName: 'baloven'},
      {firstName: 'kapysha'}
    ]
    filter(names, _=>_.firstName.startsWith('b'))
    

    TypeScript делает вывод привязок обобщенных типов на основе типов переданных аргументов. Глянем, как привязывает T для (a):

    • Исходя из сигнатуры filter TypeScript знает, что array - это массив элементов некоего типа Т.
    • TypeScript замечает, что мы передали в массив [1, 2, 3], а значит Т должен быть number
    • Везде, где TypeScript видит Т, он заменяет его на number. Следовательно параметр f: (item: T) => boolean становится f: (item: number) => boolean, а возвращаемый тип T[] становится number[].
    • TypeScript проверяет типы на совместимость и убеждается, что функция, которую мы передали как f, совместима со своей только что выведенной сигнатуры.

    Обобщенные типы - это эффективный способ выразить более обширное действие функции, чем это позволяет конкретный тип. Воспринимать же их стоит в виде ограничений. Как аннотирование параметра функции в виде n: number ограничивает значение параметра n типом number, так и использование обобщенного типа Т ограничивает тип любого привязываемого к Т условием типа быть одинаковым в каждом Т.

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

    Ну а на этом я предлагаю завершить данную статью и ознакомление с так называемыми Дженериками.

    P.S. В последующих статьях я хочу более подробно рассмотреть как привязывать конкретные типы к обобщенным, а также где можно их объявлять.

    1 ответ Последний ответ
    0

    • Войти

    • Нет учётной записи? Зарегистрироваться

    • Войдите или зарегистрируйтесь для поиска.
    • Первое сообщение
      Последнее сообщение
    0
    • Категории
    • Последние
    • Метки
    • Популярные
    • Пользователи
    • Группы