Объектно-ориентированный JavaScript: наследование на практике
В прошлой статье мы подробно рассмотрели процесс создания библиотеки для валидации данных, полученных из поля ввода. Библиотека хорошо работает и справляется со своей задачей. Тем не менее, если оценить библиотеку в более глобальном масштабе, то можно заметить, что она состоит из двух частей: валидации данных и коммуникации с DOM.
Для нетерпеливых: посмотреть весь приведённый ниже код и опробовать можно на codepen, также код доступен на gist.github.com, можно скачать zip архив с готовым примером использования.
Идея
Валидация данных не зависит от того, что будет происходит с DOM элементами, поэтому всё, что с ней связано можно выделить в отдельный модуль и использовать наследование для доступа к отдельным методам. Создадим для этих целей новый конструктор DataValidator
, который будет отвечать только за проверку переданных ему данных и на выходе предоставлять полный отчёт по проделанной работе в виде подобного объекта:
На входе конструктор принимает только сами данные и с помощью метода init
, в который передаётся объект с правилами, проводит их валидацию:
Другими словами, всё, что требуется от DataValidator
— провести проверку данных и вернуть полный отчёт по результатам. Конструктор не будет манипулировать DOM элементами, писать сообщения или выводить ошибки. С подобными вещами будут работать его потомки.
Конструктор-родитель
Процесс создания валидатора данных уже должен быть вам знаком, поэтому я не буду детально описывать каждый аспект его создания. Новый конструктор обладает лишь одним новым методом init
, который отвечает за валидацию и создание объекта об ошибках:
После получении объекта с правилами метод init
создаёт объект результатов валидации, который будет содержать четыре свойства:
data
— данные, полученные от конструктора, с которыми и будут проводиться все проверкиpassed
— массив всех правил, прошедших валидациюfailed
— массив всех правил, проваливших валидациюvalid
— свойство, показывающее были ли переданные данные полностью валидными
Объект результатов получается достаточно ёмким и решение кардинально отличается от предыдущего, где при нахождении ошибки итерации по объекту правил немедленно прекращалась. Новое подход менее оптимизированный, но, тем не менее, DataValidator
даёт полную оценку переданным ему данным и в результате пользователь получает может выбрать то, что ему нужно.
Полный код модуля с конструктором-родителем:
Дочерний конструктор
Теперь, когда у нас есть удобный способ работы с данными мы можем приступать к созданию “грязной” части — конструктора, который будет заниматься работой с DOM, писать сообщения об ошибках и вызывать callback функции. Конструктор InputValidator
должен иметь доступ к методу init
и здесь нам поможет наследование. Перед тем, как приступим к созданию конструктора, я ещё раз напомню, как он работает и что принимает на входе:
- Объект с правилами
rules
, в соответсвие с которыми и будет проводиться валидация - Объект
messages
с шаблонами сообщений, которые будут формироваться на основе ошибок валидации - Callback функции
onError
иonSuccess
, которые будут вызваны в зависимости от результатов проверки
У нас уже есть функция _createMessage
, отвечающая за генерирование понятных сообщений об ошибках, поэтому сразу добавим её в новый модуль. Также мы знаем, что при инициализации конструктора нам необходимо записать свойство element
, которое будет ссылать на переданный DOM элемент, а также свойство settings
, содержащее объект настроек.
Наследование
Конструктор DataValidator
работает со свойством data
, и это значит, что и InputValidator
должен обладать данным свойством. Разумеется, мы можем просто записать в конструкторе this.data = element.value
, но такой подход не обеспечит нам полной безопасности: со временем конструктор DataValidator
может обновиться и оперировать уже совсем другим свойством. Чтобы избежать подобных проблем достаточно вызвать функцию-конструктор родителя без ключевого слова new
внутри конструктора потомка. Подобное подход позволит записать все необходимые свойства, выборочно с помощью метода call
или полностью с помощью apply
(подробнее об использовании конструкторов, как обычных функций):
В данном случае мы не можем использовать метод apply
, так как хотим передать не список аргументов, а значение, получаемое в результате операции с исходным аргументом.
Итак, у нас есть свойство data
, а значит мы можем приступить к наследованию. Всё реализуется достаточно просто — необходимо всего лишь перезаписать прототип InputValidator
с помощью Object.create
:
Теперь у нас есть доступ ко всем свойствам DataValidator
внутри конструктора InputValidator
, в том числе и к необходимому нам свойству init
. Остаётся создать ещё один метод validate
, который будет проводить валидацию и заниматься “грязной” работой:
При каждом запуске метода validate
мы хотим перезаписать свойство data
, чтобы всегда проверять свежие данные. После проведения валидации мы хотим записать первый неудачный результат в переменную failed
, на основе которой и будет строиться сообщение об ошибке.
На самом деле, записывать свойство data
при инициализации конструктора InputValidator
необязательно, так как оно в любом случае будет перезаписано при использовании метода validate
.
Весь код модуля InputValidator
:
Расширение возможностей
Прошлая версия библиотеки располагала достаточно скудным набором методов для валидации данных, так как предполагалось, что пользователь сам допишет всё, что ему будет необходимо. В новой версии добавлять свои способы для валидации данных настолько же просто, но новые методы необходимо добавлять родителю DataValidator
:
Посмотреть весь приведённый выше код и опробовать можно на codepen, также код доступен на gist.github.com, можно скачать zip архив с готовым примером использования.
Комментарии