Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Инварианты JS: Абстракция с помощью данных

Абстракция позволяет нам не думать о деталях реализации и сосредоточиться на её использовании. Более того, при необходимости реализацию абстракции можно всегда переписать, не боясь сломать использующий её код. Но есть ещё одна важная причина, по которой нужно использовать абстракцию — соблюдение инвариантов.

Инвариант в программировании — логическое выражение, определяющее непротиворечивость состояния (набора данных).

Разберёмся на примере. Когда мы описали конструктор и селекторы для рациональных чисел, то неявно подразумевали выполнение следующих инвариантов:

const num = makeRational(numer, denom);
numer === getNumer(num); // true
denom === getDenom(num); // true

// Или с учётом нормализации
// numer / denom === getNumer(num) / getDenom(num);

Передав в конструктор рационального числа числитель и знаменатель, мы ожидаем, что получим их (те же числа), если применим селекторы к этому рациональному числу. Именно так определяется корректность работы данной абстракции. Этот код практически является тестами!

Инварианты существуют относительно любой операции. И иногда они довольно хитрые. Например, рациональные числа можно сравнивать между собой, но не прямым способом, потому, что одни и те же дроби можно представлять разными способами: 1/2 и 2/4. Код, который не учитывает этого факта, работает некорректно:

const num1 = makeRational(2, 4);
const num2 = makeRational(8, 16);

console.log(num1 === num2); // false

Задача приведения дроби к нормальной форме называется нормализацией. В это понятие входит несколько операций, например, сокращение дроби, определение знака, перенос знака в числитель. Реализовать нормализацию можно разными способами. Самый очевидный — выполнять её во время создания дроби, внутри функции makeRational(). Другой — выполнять нормализацию уже при обращении через функции getNumer() и getDenom(). Последний способ обладает недостатком — вычисление нормальной формы происходит на каждый вызов. Избежать этого можно, используя технику мемоизации.

Учитывая новые вводные, становится понятно, что инвариант, связывающий конструктор и селекторы, нуждается в модификации. Функции getNumer() и getDenom() должны вернуть не переданные значения, а значения после нормализации (если дробь уже нормализована, то это будут те же самые значения).

const num = makeRational(10, 20);
getNumer(num); // 1
getDenom(num); // 2

Абстракция не только прячет от нас реализацию, но и отвечает за соблюдение инвариантов. Любая работа в обход абстракции чревата тем, что не будут учтены внутренние преобразования:

// Обход конструктора

// Эти данные не нормализованы, потому что не использовался конструктор
const num = { numer: 10, denom: 20 };

// Возвращается не то, что должно (ожидается нормализованный возврат):
getNumer(num); // 10
getDenom(num); // 20

// Прямая модификация

const num = makeRational(10, 20);
// тут не может быть нормализации, так как прямое изменение
num.numer = 40 

getNumer(num); // 40
getDenom(num); // 2

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

Глядя на примеры выше, возникает закономерный вопрос. А можно ли сделать так, чтобы обойти абстракцию было нельзя? Глобально – да. Такой подход называют сокрытием данных (data hiding). Обычно для обеспечения сокрытия в языках используется специальный синтаксис. Однако, защиту данных можно организовать и без специальных средств, только за счёт функций высшего порядка. Данный способ основан на создании абстракций с помощью анонимных функций, замыканий и передачи сообщений (подробнее в SICP). Если вы хотите узнать об этом больше, то попробуйте наш курс JS: Составные данные


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 18 апреля
профессия
от 9 900 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 18 апреля
профессия
от 6 300 ₽ в месяц
Разработка бэкенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 18 апреля

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»