Абстракции позволяют нам моделировать необходимую предметную область. Попробуем понять это утверждение на конкретном примере. Допустим, мы хотим сделать программу-каталог для управления коллекцией книг. Вы можете сразу кинуться писать код, но так делать не стоит.
В первую очередь необходимо проанализировать предметную область (книги + каталоги) и сформировать базовую модель. Нужно начать с определения сущностей и операций над ними. В случае нашего каталога у нас есть по крайней мере две сущности: книга и коллекция. Базовыми операциями сделаем добавление книги в коллекцию и удаление ее из коллекции.
Напишем код операции для добавления сущности в коллекцию:
book1 = make_book('The Comedy of Errors')
book2 = make_book('The Adventures of Tom Sawyer')
coll = makeColl('Adventures')
updated_Coll_1 = add_Book_To_Coll(coll, book1)
updated_Coll_2 = add_Book_To_Coll(updated_Coll_1, book2)
Мы создаем две книги, одну коллекцию и добавляем эти книги в коллекцию. Что значит "создали книгу"? Что такое "книга"? Книга — это сущность, которая ведет себя как книга. Что это значит?
Так как мы работаем с языком программирования, то конструктор make_book()
, создающий книгу, возвращает какие-то данные: примитивные (число, строку) или составные, используя пары. Главное — нам не важно, как они устроены.
Организовать эти данные можно тысячью разных способов, и конкретный способ зависит от разработчика, его предпочтений и данных, которые необходимо хранить. То, что книга является книгой, определяется не ее внутренним устройством, а тем, что к ней применим набор операций, созданный для книг. Например, операция «получить имя книги». Сами операции знают, как устроены данные, иначе они не смогли бы ими манипулировать. Запомните: это детали реализации. Не заглянув внутрь операций, мы не узнаем, как они устроены, но нам это и не нужно.
В этом и заключается вся суть абстракции. Мы знаем, как создать сущность и какие операции к ней применимы. Обычно это и называется дизайном кода, а сами операции + конструктор — это API или интерфейс модуля/пакета/библиотеки.
Получаем следующий алгоритм:
- Анализируем предметную область и выделяем сущности
- Реализуем конструктор, а затем выбираем внутреннее представление сущностей на основе того, что мы планируем в них хранить
- Реализуем необходимые операции
В примере с книгами видно, что мы храним только название. А это значит, что нам достаточно хранить одну строчку.
Тогда реализация конструктора будет такой:
def make_book(name):
return name
book = make_book('The Comedy of Errors')
print(to_string(book)) # The Comedy of Errors
А что, если мы захотим дополнительно хранить версию издания книги? Тогда мы можем использовать пары для хранения составных данных, и у нас уже появляются составные данные, так как нужно хранить два параметра.
def make_book(name, rev):
return cons(name, rev)
book = make_book('The Adventures of Tom Sawyer', 3)
print(to_string(book)) # (The Adventures of Tom Sawyer, 3)
Поменяется ли клиентский код, использующий нашу библиотеку, при изменении внутренней структуры? Как видно из примеров выше, ничего не поменяется, кроме вызова конструкторов. Это и есть хорошая абстракция, при которой наш код не рассыпается в случае любых изменений внутренней реализации.
Примеры других абстракций
Пример с книгами — это всего лишь один из многих вариантов. Программисты в своей работе постоянно перекладывают сущности реального мира на код, создавая всевозможные абстракции данных. Давайте попробуем пофантазировать и накидать еще вариантов. Мы добавим только три, а еще три додумайте сами. В реальной жизни абстракции будут сложнее, так как должны включать больше данных, но суть от этого не поменяется.
Товар. Все, что продается и покупается. Предположим, что нам интересны только два параметра: цена и название. Саму сущность можно назвать
product
. Тогда наша абстракция будет содержать как минимум три следующих функции:# интерфейсные функции (абстракция) def make_product(name, cost): return cons(name, cost) def get_name(product): return car(product) def get_cost(product): return cdr(product) # использование product = make_product('parmesan cheese', 100) get_name(product) # parmesan cheese get_cost(product) # 100
Текстовый документ. Например, как в Google Docs. В нашем примере он будет состоять из названия и содержимого.
# интерфейсные функции (абстракция) def make_document(name, body): return cons(name, body) def get_name(document): return car(document) def get_body(document): return cdr(document) # использование document = make_document('Some words about Hexlet', 'Some text') get_name(document) # Some words about hexlet get_body(document) # Some text
А вот пример, в котором нужно хранить не два значения, а три. Это точка в пространстве.
# интерфейсные функции (абстракция) def make_3d_point(x, y, z): return cons(cons(x, y), z) # альтернатива cons(x, cons(y, z)) def get_x(point): return car(car(point)) def get_y(point): return cdr(car(point)) def get_z(point): return cdr(point) # использование point = make_3d_point(1, 10, -3)
И теперь самое интересное: любая абстракция, построенная таким образом, может выступать в роли строительных блоков в другой абстракции.
Предположим, что мы продаем документы как продукты:
document = make_document('Some words about Hexlet', 'Some text')
product = make_product(document, 100)
А дальше все по аналогии.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.