Зарегистрируйтесь, чтобы продолжить обучение

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

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

В этом уроке мы познакомимся с инвариантами, нормализацией и понятием 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.


Дополнительные материалы

  1. Мемоизация
  2. Reflection
  3. Курс "Составные данные"

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

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

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

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

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка веб-приложений на Django
10 месяцев
с нуля
Старт 26 декабря

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

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

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

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