Абстракция позволяет нам не думать о деталях реализации и сосредоточиться на ее использовании. Также при необходимости реализацию абстракции можно всегда переписать и не бояться сломать использующий ее код. Но есть еще одна важная причина, по которой нужно использовать абстракцию — соблюдение инвариантов.
В этом уроке мы познакомимся с инвариантами, нормализацией и понятием data hiding.
Инварианты
Инвариант в программировании — логическое выражение, которое определяет непротиворечивость состояния — набора данных.
Разберемся на примере. Когда мы описали конструктор и селекторы для рациональных чисел, то неявно подразумевали выполнение следующих инвариантов:
num = make_rational(numer, denom)
numer == get_numer(num)
# True
denom == get_denom(num)
# True
Когда мы передаем в конструктор рационального числа числитель и знаменатель, то ожидаем, что получим те же числа, если применим селекторы к этому рациональному числу. Так определяется корректность работы данной абстракции. Этот код практически является тестами.
Инварианты существуют относительно любой операции. И иногда они довольно хитрые. Например, рациональные числа можно сравнивать между собой, но не прямым способом. Одни и те же дроби можно представлять разными способами: 1/2 и 2/4. Код, который не учитывает этого факта, работает некорректно:
num1 = make_rational(2, 4)
num2 = make_rational(8, 16)
num1 == num2
# False
Нормализация
Задача приведения дроби к нормальной форме называется нормализацией. Реализовать ее можно разными способами. Самый очевидный — выполнять нормализацию во время создания дроби, внутри функции make_rational
. Другой — выполнять нормализацию уже при обращении через функции get_numer
и get_denom
.
Последний способ обладает недостатком — вычисление нормальной формы происходит на каждый вызов. Избежать этого можно с помощью техники мемоизация.
Учитывая новые вводные, становится понятно, что инвариант, который связывает конструктор и селекторы, нуждается в модификации. Функции get_numer
и get_denom
должны вернуть не переданные значения, а значения после нормализации:
num = make_rational(10, 20)
get_numer(num)
# 1
get_denom(num)
# 2
Если дробь уже нормализована, то это будут те же самые значения.
Абстракция не только прячет от нас реализацию, но и отвечает за соблюдение инвариантов. Если работать в обход абстракции, то внутренние преобразования не будут учтены:
# Обход конструктора
# Эти данные не нормализованы,
# потому что не использовался конструктор
num = {"numer": 10, "denom": 20}
# Возвращается не то, что должно
# (ожидается нормализованный возврат)
get_numer(num)
# 10
get_denom(num)
# 20
# Прямая модификация
num = make_rational(10, 20)
# Тут не может быть нормализации,
# так как прямое изменениe
num['numer'] = 40
get_numer(num)
# 40
get_denom(num)
# 20
Если работать с данными напрямую, минуя абстракцию, можно сломать инварианты, которые обеспечивались дополнительной логикой в конструкторе или селекторах. Поэтому важно пользоваться кодом так, как было задумано авторами.
Сокрытие данных
Можно сделать так, чтобы обойти абстракцию было нельзя. Такой подход называют сокрытием данных — data hiding.
Чтобы обеспечить сокрытие, в языках используется специальный синтаксис. Однако защиту данных можно организовать и без специальных средств, только за счет функций высшего порядка. Такой способ основан на создании абстракций с помощью анонимных функций, замыканий и передачи сообщений. Если вы хотите узнать об этом больше, то пройдите курс Python: Составные данные.
В реальности подобные механизмы легко обходятся с помощью Reflection API. Это можно сделать даже без них — за счет ссылочных данных. Также есть немало языков, например, JavaScript, в которых все нормально с абстракциями, но нет механизмов для защиты данных. При этом ничего страшного не произошло.
На практике при использовании абстракций никто не пытается специально их нарушать. Возможно, значение принудительной защиты данных сильно преувеличено.
Выводы
В этом уроке мы познакомились с инвариантами. Они используются, чтобы определять непротиворечивость состояния и существуют относительно любой операции. Также узнали, что такое нормализация — когда дробь приводится к нормальной форме. И выяснили, что можно сделать так, чтобы обойти абстракцию было нельзя. Это возможно с помощью сокрытия данных — data hiding.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.