В этом уроке мы рассмотрим еще одну систему — рациональные числа и операции над ними, а также научимся видеть барьеры абстракции и выделять слои.
Рациональное число
Рациональным называют число, которое может быть представлено в виде дроби a/b
, где а — это числитель дроби, b — знаменатель дроби. Причем b не должно быть нулем, так как делить на нуль нельзя.
Рациональные числа в Python не поддерживаются, поэтому абстракцию для них создадим самостоятельно. Нам понадобятся конструктор и селекторы:
# Создали рациональное число "одна вторая"
num = make_rational(1, 2)
numer = get_numer(num)
# 1
denom = get_denom(num)
# 2
С помощью трех функций мы определили рациональное число. Конструктор собирает его из частей, селекторы позволяют извлечь каждую часть. Чем при этом является num
с точки зрения языка — не важно. Это может быть функция, список или словарь. Во внутренней реализации можно использовать даже строки:
def make_rational(numer, denom):
return f"{numer}/{denom}"
def get_numer(rational):
numer, _ = rational.split('/')
return numer
def get_denom(rational):
_, denom = rational.split('/')
return denom
make_rational(10, 3)
# "10/3"
Мы научились представлять рациональные числа, но эта абстракция малоприменима. Она становится полезна, когда появляется возможность оперировать ей.
Для рациональных чисел базовыми операциями можно считать арифметические, например, сложение, вычитание или умножение. Умножение рациональных чисел — самая простая операция. Для ее выполнения нужно перемножить числители и знаменатели:
3/4 * 4/5 = (3 * 4)/(4 * 5) = 12/20
Если предположить, что реальная структура рационального числа выглядит так: {"numer": 2, "denom": 3}
, то решение может быть таким:
def multiply_rational(rational1, rational2):
return {
"numer": rational1["numer"] * rational2["numer"],
"denom": rational1["denom"] * rational2["denom"],
}
С точки зрения вызывающего кода абстракция сохранена. На вход в multiply_rational
подаются рациональные числа, на выходе — рациональное число. Но внутри абстракции нет, так как обращение с рациональными числами строится на основе знания их устройства.
Чтобы изменить внутреннюю реализацию рациональных чисел, придется переписать все операции, которые работают с рациональными числами напрямую — то есть без селекторов или конструктора. Данный код нарушает принцип одного уровня абстракции — single layer abstraction.
Уровневое проектирование
При разработке сложных систем используется подход — уровневое проектирование. В его случае системе придается структура с помощью последовательных уровней. Каждый из уровней строится путем комбинации частей, которые на данном уровне рассматриваются как элементарные. Части, которые строятся на каждом уровне, работают как элементарные на следующем:
Уровневое проектирование проходит через всю технику построения сложных систем. Например, в разработке программного обеспечения уровневое проектирование может начинаться с создания низкоуровневых функций и библиотек, которые решают конкретные задачи. К таким задачам могут относиться обработка данных, сетевое взаимодействие или графический интерфейс.
Затем эти функции и библиотеки объединяются для создания модулей, которые предоставляют более высокоуровневые возможности. Модули можно использовать, чтобы построить полноценные приложения, которые включают большое количество функциональных возможностей и обеспечивают решение более крупных задач.
Посмотрим на пример кода:
def multiply_rational(rational1, rational2):
return make_rational(
get_numer(rational1) * get_numer(rational2),
get_denom(rational1) * get_denom(rational2),
)
Здесь базовым уровнем являются типы, которые встроены в сам язык: числа и словарь. На их основе сформирован уровень для представления рациональных чисел: make_rational
, get_denom
, get_numer
. Затем — уровень, на котором реализованы арифметические операции над рациональными числами: сложение, вычитание, умножение и так далее.
Речь идет про реализацию самих уровней. Например, операция сложения полностью опирается на конструктор и селекторы, но ничего не знает и не может знать про внутреннее устройство самих рациональных чисел. С другой стороны, это не значит, что в одном месте не могут появиться функции из разных уровней. Это нормально во многих случаях. Например:
def f(rational1, rational2):
rational3 = multiply_rational(rational1, rational2)
denom = get_denom(rational3)
numer = get_numer(rational3)
print(f"Denom: {denom}")
print(f"Numer: {numer}")
Выводы
В этом уроке мы рассмотрели еще одну систему — рациональные числа и операции над ними. Так как в Python рациональные числа не поддерживаются, нам предстояло самостоятельно создать для них абстракцию.
Также мы разобрались, как видеть барьеры абстракции и выделять слои. Мы узнали, что такое уровневое проектирование. Оно применяется при разработке сложных систем и проходит через всю технику их построения.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.