Метапрограмування на Typescript, або як декоратори допомагають у вирішенні повсякденних задач
Всім привіт! Мене звати Віктор Мороз і останні 4 чотири роки я працюю в компанії Creatio. Ми розробляємо No-Code/Low-Code платформу, що дозволяє кінцевим користувачам кастомізувати продукт під свої вимоги. Однією із зон відповідальності моєї команди є безпосередньо API розширення системи сторонніми елементами.
Останні 5 років я активно розробляю UI з використанням фреймворку Angular і коли перед нами постала подібна задача, дуже хотілось надати нашій системі той рівень гнучкості, якого в Angular досягли використанням декораторів.
Декоратор як шаблон проєктування
Декоратор — структурний шаблон проєктування, призначений для динамічного додавання нової поведінки до об’єкта шляхом створення обгортки над вихідним об’єктом зі збереженням початкового API. Шаблон надає гнучку альтернативу методу визначення підкласів, якщо потрібно розширити функціональність.
Гарну аналогію з життя приводять автори в описі шаблону
Будь-який одяг — це аналог Декоратора. Застосовуючи Декоратор, ви не змінюєте початковий клас і не створюєте дочірніх класів. Так само з одягом: вдягаючи светра, ви не перестаєте бути собою, але отримуєте нову властивість — захист від холоду. Ви можете піти далі й одягти зверху ще один декоратор — плащ, щоб захиститися від дощу.
Зображення з ресурсу refactoring.guru
Декоратори в TypeScript
У мові TypeScript підтримка декораторів з’явилася з версії 1.5 й активно використовується в багатьох фреймворках і бібліотеках, таких як Angular, Nest.js, TypeORM, InversifyJS.
Наразі підтримуються такі види декораторів:
- Декоратор класу.
- Декоратор поля.
- Декоратор метода.
- Декоратор властивості.
- Декоратор параметрів.
Далі ми детально розглянемо кожен із них.
Декоратор класу
Використовується для розширення наявного класу деякими властивостями та методами.
Для прикладу можна розглянути декоратор, який робить клас недоступним для наслідування:
Декоратор поля
Декоратори поля можна використовувати для формування мета-інформації про властивості класу, а також додавання нових методів та властивостей.
Прикладом такого декоратора може бути декоратор для генерації id підчас створення екземпляра класу:
Декоратор метода
Відрізняється від декоратора поля тим, що в параметри додатково отримує дескриптор. Останній дозволяє перевизначити вихідну реалізацію і впровадити туди додаткову логіку.
У дескрипторі містяться такі властивості:
- value
- writable
- enumerable
- configurable
Дуже часто цей тип декораторів зустрічається в момент, коли необхідно додати якусь логіку до чи після виконання методу. Класичним прикладом такого використання можна назвати декоратор для логування:
Декоратор властивостей
Декоратор властивостей, в цілому, схожий на декоратор методу, різниця полягає у властивостях в дескрипторі.
У дескрипторі властивості містяться такі властивості:
- get
- set
- enumerable
- configurable
Декоратор параметра
Автономний декоратор параметра мало що може зробити. Зазвичай, він використовується для запису інформації, яку можуть використовувати інші декоратори.
Дуже часто їх створюють для реєстрації інформації, яка в подальшому використовується для трансформації значення параметра. Розробники фреймворку NestJS активно їх використовують під час створення контролерів.
Порядок виконання декораторів
Комбінуючи використання різних декораторів, можливо реалізовувати більш складні кейси, але для цього важливо розуміти порядок їх виконання.
Декоратори виконуються в наступному порядку:
- До кожного члена екземпляра застосовуються декоратори параметрів, а потім декоратори методу, властивості або поля.
- До кожного статичного члена застосовуються декоратори параметрів, а потім декоратори методу, властивості або поля.
- Для конструктора застосовуються декоратори параметрів.
- Для класу застосовуються декоратори класу.
Reflection
Рефлексія — це здатність мови програмування досліджувати та змінювати свої внутрішні властивості та поведінку класів. Ця можливість є у багатьох мовах програмування, таких як C#, Java та інших.
У JavaScript об’єкт Reflect з’явився порівняно недавно, його можна використати для виклику методів, конструювання об’єктів, читання та встановлення значення властивостей, маніпуляції та розширення властивостей.
Спільне використання рефлексії з декораторами дозволяє реалізовувати низку цікавих кейсів. Наприклад, за допомогою пакета reflect-metadata ми можемо отримати доступ до типу даних, який був задекларований під час написання коду для реалізації декоратора, що валідує вхідні параметри метода:
На поточний момент доступними є три типи design-time анотації:
- design:type — тип властивості
- design:paramtypes — тип параметра метода
- design:returntype — повертаємий методом тип данных
Результатом цих трьох типів є функції-конструктори (такі як String та Number). Значення визначається за наступними правилами:
- number -> Number
- string — String
- boolean — Boolean
- void/null/never — undefined
- Array/Tuple — Array
- Class — конструктор класу
- Enum — Number, коли перечислення числове, або Object
- Function — Function
Використання декораторів в Creatio
Платформа Creatio передбачає можливість розширення набору системних елементів згідно з вимогами кожного користувача. Щоб спростити процес реєстрації користувальницьких елементів, в системі можна використовувати декоратори, не знаючи, куди саме декоровані елементи потрібно реєструвати й як надалі система буде їх використовувати.
Однією з таких задач може бути додавання нового візуального елемента для використання на сторінці.
Щоб система могла взаємодіяти з користувальницьким елементом, їй потрібно знати, які вхідні властивості та вихідні події підтримуються елементом.
Для реєстрації цієї інформації ми використовуємо декоратор CrtViewElement, CrtInput, CrtOutput.
Декоратори властивостей CrtInput та CrtOutput формують метаопис візуального елемента. Реалізація обох декораторів однакова, нижче наведено приклад декоратора для вхідної властивості.
Сформована інформація використовується декоратором CrtViewElement.
Візуальні елементи об’єднуються в модулі:
Тепер у нас сформований весь метаопис візуального елементу, залишилося тільки, щоб система почала його використовувати. Для цього необхідно викликати функцію bootstrapCrtModule .
Висновок
Використання декораторів дозволило підвищити гнучкість системи і спростити реєстрацію елементів для використання у системі. Код використання декораторів виглядає лаконічно та дозволяє розробникам додавати метадані необхідних елементів декларативно, що дозволяє зосередитися на реалізації бізнес задачі, а не на питаннях інтеграції рішень в хост-систему.
Загалом кейсів використання декораторів дуже багато, наприклад:
- Додавання логіки до/після виклику метода.
- Трансформація параметрів перед викликом метода.
- Додавання додаткових методів чи властивостей.
- Валідація типів даних під час виконання.
- Dependency Injection.
Якщо у вас є ідеї, в яких ще повсякденних задачах можливо та доцільно було б використати декоратори, поділіться, будь ласка, у коментарях.
Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті
25 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів