- PVSM.RU - https://www.pvsm.ru -
В процессе описания очередного набора тестов для модуля Node.js поймал себя на мысли "опять проверка типов". Каждый параметр метода класса, каждое свойство устанавливаемое с помощью сеттера надо проверять. Можно конечно просто забить или дополнять все кодом реализующим проверки или попробовать описать все декораторами. Но в этот раз поступим немного иначе.
Приход спецификации ES6 и реализации ее в различных движка дарит нам много удивительных вещей, по-моему глупо стоять в стороне от этого праздника. Вооружившись желанием поглубже погрузиться в новую спецификацию и немного упростить себе жизнь, попробуем реализовать анализ типов с помощью такой замечательной штуки как Proxy [1]. Конечно мы будем использовать и другие плюшки ES6, нативно поддерживаемые Node.js версии 6.1.0, такие как: классы, map'ы, стрелочные функции и др.
Устанавливаем пакет npm i typedproxy
. Подключаем модуль
//пункт 1.
const Typed = require('typedproxy');
Создаем класс, используем статические методы, методы, статические свойства и свойства. При описании параметров методов и сеттеров используем специальный синтаксис.
А именно: каждое имя параметра, используемое в методах (в т.ч. статических) должно начинаться с последовательности символов соответствующих типу. Тип — это ничто иное, как свойство так называемого объекта типов. Где имя свойства соответствует названию типа, а значение свойства является функцией реализующей проверку переданного значения. Другими словами можно определить сколько угодно своих типов переменных.
Подробнее о объекте типов. В данном объекте должны быть перечислены все типы которые используются, или планируются к использованию в вашем классе. Если вы забыли выполнить указанное условие — это приведет к RangeError в процессе выполнения.
И так немного кода для понимания принципов:
//пункт 2. Создаем класс, описывая только конструктор.
class TestClass {
//пункт 3. Конструктор должен принимать параметр с типом myRange.
constructor(myRangeValue){
this.value = myRangeValue;
}
};
//пункт 4. Создаем объект типов. Здесь мы видим используемый ранее myRange.
const types = {
'myRange' : (value) => {
if(value < 0 && value > 10) {
throw new TypeError(`parameter must be more than 0 and less than 10, not ${value}`);
}
}
};
Как мы видим myRangeValue — это имя параметра, проверка которого определяется в свойстве объекта типов с соответствующим именем myRange.
Теперь, что бы включить проверку типов необходимо сделать класс типизированным (это понятие конечно используется в рамках используемого модуля, не стоит здесь притягивать понятия из спецификации). А делаем мы это так, имея ранее описанные класс TestClass и типы:
//пункт 5.
const TypedTestClass = new Typed(TestClass, types);
Выше мы получили новый класс TypedTestClass, который на самом деле является экземпляром Proxy, но об этом после [2]. Его мы используем вместо TestClass, то есть создаем экземпляры, вызываем статические методы, как самого класса, так и его экземпляром. Вообщем делая все то, что хотели сделать с первоначальным классом TestClass.
//пункт 6. Создадим несколько экземпляров класса.
/*ok - параметр конструктора проходит проверку*/
const instance1 = new TypedTestClass(5);
/*TypeError - параметр конструктора не прошел проверку*/
const instance2 = new TypedTestClass(11);
/*RangeError - количество параметров ожидаемых конструктором не соответствует
количеству переданных в него параметров*/
const instance3 = new TypedTestClass();
/*RangeError - количество параметров ожидаемых конструктором не соответствует
количеству переданных в него параметров*/
const instance3 = new TypedTestClass(1, 2);
Как можно заметить передача неверного типа параметра теперь вызывает ошибку. Передача неверного количества параметров (не важно удовлетворяют ли они требованиям типа или нет) также вызывает ошибку.
extends
) типизировать нужно конечный класс, а не всю цепочку. Ну во-первых зачем лишние переменные и лишний труд, а во-вторых у нас просто ничего не выйдет.//пункт 1.
const Typed = require('typedproxy');
//пункт 2. Создаем класс, описывая только конструктор.
class TestClass {
//пункт 3. Конструктор должен принимать параметр с типом myRange.
constructor(myRangeValue){
this.value = myRangeValue;
}
};
//пункт 4. Создаем объект типов. Здесь мы видим используемый ранее myRange.
const types = {
'myRange' : (value) => {
if(value < 0 && value > 10) {
throw new TypeError(`parameter must be more than 0 and less than 10, not ${value}`);
}
}
};
//пункт 5.
const TypedTestClass = new Typed(TestClass, types);
//пункт 6. Создадим несколько экземпляров класса.
/*ok - параметр конструктора проходит проверку*/
const instance1 = new TypedTestClass(5);
/*TypeError - параметр конструктора не прошел проверку*/
const instance2 = new TypedTestClass(11);
/*RangeError - количество параметров ожидаемых конструктором не соответствует
количеству переданных в него параметров*/
const instance3 = new TypedTestClass();
/*RangeError - количество параметров ожидаемых конструктором не соответствует
количеству переданных в него параметров*/
const instance3 = new TypedTestClass(1, 2);
Для тех, кому код понятнее тысячи слов и пары картинок: проект с тестами здесь [3]. Для тех кто сохранил желание понять как это работает в словах, попробую объяснить далее.
Как видно на рисунке и как было сказано выше необходимо установить прямую взаимосвязь между именами параметров методов и типами используемыми в нашем классе. То есть имя параметра, должно начинаться с последовательности символов соответствующих типу.
Это необходимо что бы заработала функция производящая проверку типа (о ней скажем немного позже) [4]. В принципе, если реализация данной функции вас не устраивает, можете передать свою третьим параметром, при создании типизированного класса.
const Typed = require('typedproxy');
class TestClass {
//описание класса...
};
const types = {
//описание типов...
};
const TypedTestClass = Typed(TestClass, types, (types, someFunction, ...args) => {/*реализация функции проверки типов*/});
При типизации класса создается и возвращается новый Proxy. Этот самый прокси и является классом осуществляющим анализ типов. Его суть состоит в применении функции проверки типов [4] и определении необходимых ловушек [5] для перехвата вызова статических методов, создания новых экземпляров и т.д.
Функция проверки типов (зелено-красные квадраты на рисунке) работает следующим образом:
Новых прокси содержит только три ловушки: get, set и construct.
new
производит проверку типов параметров передающихся в конструктор. После этого создает экземпляр первоначального класса и прокси на его основе (с двумя ловушками get и set, которые работаю похожими с указанными выше способами 1 и 2).Конечно это выглядит достаточно монструозно и на самом деле так и есть. В этом модуле отображена лишь идея требующая доработок и правок. Мне даже не хочется думать о производительности, скорее этим можно пользоваться жертвуя ей в угоду удобству и экономии времени.
Автор: antonecma
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/120965
Ссылки в тексте:
[1] Proxy: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
[2] после: #underthehood
[3] здесь: https://github.com/antonecma/TypedProxy
[4] (о ней скажем немного позже): #typeTester
[5] ловушек: #interceptions
[6] Источник: https://habrahabr.ru/post/301122/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.