





























Йонатан Шарвит
Санкт-Петербург
«БХВ-Петербург»
2024
УДК 004.4'236
ББК 32.973.26-018.1
Ш25
Шарвит Й.
Ш25
Дата-ориентированное программирование: Пер. с англ. — СПб.:
БХВ-Петербург, 2024. — 464 с.: ил.
ISBN 978-5-9775-1924-3
Книга посвящена парадигме DOP (дата-ориентированному программированию), являющейся расширением философии объектно-ориентированного программирования. Предлагается новый взгляд на формирование структур данных и операции над ними в высоконагруженных приложениях. Изложенный материал дает
решения сложных проблем, возникающих при управлении состоянием, разделяе-мыми и распределенными данными, позволяет безопасно организовать параллелизм и конкурентность, а также адаптировать ключевые принципы объектно-ориентированного программирования (полиморфизм, композицию, наследование) к новым задачам, связанным с обработкой больших данных.
Для аналитиков данных, программистов,
руководителей команд и преподавателей
УДК 004.4'236
ББК 32.973.26-018.1
Группа подготовки издания:
Руководитель проекта
Олег Сивченко
Зав. редакцией
Людмила Гауль
Перевод с английского
Юлии Спикиной
Редактор
Софья Исакова
Компьютерная верстка
Ольги Сергиенко
Оформление обложки
Зои Канторович
© 2023 BHV
Authorized translation of the English edition © 2022 Manning Publications.
This translation is published and sold by permission of Manning Publications, the owner of all rights to publish and sell the same.
Авторизованный перевод с английского языка на русский издания © 2022 Manning Publications.
Перевод опубликован и продается с разрешения компании-правообладателя Manning Publications.
Все права защищены.
"БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20
ISBN 978-1-61729-857-8 (англ.)
© Manning Publications, 2022
ISBN 978-5-9775-1924-3 (рус.)
© Перевод на русский язык, оформление. ООО "БХВ-Петербург", ООО "БХВ", 2024
Оглавление
Вступительное слово .................................................................................................... 13
Введение .......................................................................................................................... 17
Благодарности ................................................................................................................................ 18
Об этой книге ................................................................................................................................. 19
Кто должен прочитать эту книгу? ........................................................................................ 19
Как организована эта книга: дорожная карта ...................................................................... 19
О коде...................................................................................................................................... 21
Дискуссионный форум liveBook ........................................................................................... 22
Об авторе ........................................................................................................................................ 23
Об иллюстрации на обложке ........................................................................................................ 23
Персонажи пьесы ........................................................................................................................... 23
ЧАСТЬ 1. ГИБКОСТЬ ................................................................................................. 25
Глава 1. Сложность объектно-ориентированного программирования ............... 27
1.1. Дизайн ООП: классический или традиционный? ................................................................ 28
1.1.1. Этап проектирования ................................................................................................... 28
1.1.2. UML 101 ....................................................................................................................... 30
1.1.3. Объяснение каждой части диаграммы классов ......................................................... 33
Класс Library ....................................................................................................................... 33
Классы Librarian, Member и User ...................................................................................... 33
Класс Catalog ...................................................................................................................... 35
Класс Book ........................................................................................................................... 35
Класс BookItem .................................................................................................................... 35
1.1.4. Этап реализации ........................................................................................................... 36
1.2. Источники сложности ............................................................................................................ 36
1.2.1. Множество отношений между классами ................................................................... 38
1.2.2. Непредсказуемое поведение кода............................................................................... 40
1.2.3. Нетривиальная сериализация данных ........................................................................ 42
1.2.4. Сложные иерархии классов ........................................................................................ 44
Итоги ............................................................................................................................................... 48
6
Оглавление
Глава 2. Разделение кода и данных ........................................................................... 51
2.1. Две части системы ДОП......................................................................................................... 52
2.2. Объекты данных ..................................................................................................................... 54
2.3. Модули кода ............................................................................................................................ 56
2.4. Системы ДОП просты для понимания .................................................................................. 61
2.5. Системы ДОП являются гибкими ......................................................................................... 64
Итоги ............................................................................................................................................... 68
Глава 3. Основные манипуляции с данными .......................................................... 69
3.1. Разработка модели данных .................................................................................................... 70
3.2. Представление записей в виде карт ...................................................................................... 74
3.3. Манипулирование данными с помощью универсальных функций .................................... 81
3.4. Вычисление результатов поиска ........................................................................................... 85
3.5. Обработка записей различных типов .................................................................................... 93
Итоги ............................................................................................................................................... 98
Глава 4. Управление состоянием ............................................................................. 101
4.1. Несколько версий системных данных ................................................................................ 102
4.2. Структурное совместное использование ............................................................................ 104
4.3. Реализация структурного совместного использования ..................................................... 110
4.4. Безопасность данных ............................................................................................................ 112
4.5. Фиксационный этап изменения ........................................................................................... 114
4.6. Обеспечение целостности состояния системы ................................................................... 116
4.7. Восстановление предыдущих состояний ............................................................................ 117
Итоги ............................................................................................................................................. 120
Глава 5. Основы контроля конкурентности .......................................................... 123
5.1. Оптимистичный контроль конкурентности ........................................................................ 124
5.2. Согласование между конкурентными изменениями.......................................................... 126
5.3. Сокращение коллекций ........................................................................................................ 129
5.4. Структурная разница ............................................................................................................ 131
5.5. Имплементация алгоритма согласования ........................................................................... 140
Итоги ............................................................................................................................................. 142
Глава 6. Модульные тесты ........................................................................................ 145
6.1. Простота дата-ориентированных тестовых кейсов ............................................................ 145
6.2. Модульные тесты для кода манипулирования данными ................................................... 147
6.2.1. Дерево вызовов функций .......................................................................................... 149
6.2.2. Модульные тесты для функций вниз по дереву ...................................................... 150
6.2.3. Модульные тесты для узлов в дереве ....................................................................... 154
6.3. Модульные тесты для запросов ........................................................................................... 157
6.4. Модульные мутационные тесты .......................................................................................... 162
Движение вперед ......................................................................................................................... 171
Итоги ............................................................................................................................................. 172
ЧАСТЬ 2. МАСШТАБИРУЕМОСТЬ ...................................................................... 175
Глава 7. Основы валидации данных ....................................................................... 179
7.1. Валидация данных в ДОП .................................................................................................... 179
7.2. Суть JSON-схемы ................................................................................................................. 181
Оглавление
7
7.3. Гибкость и строгость схемы ................................................................................................ 188
7.4. Композиция схемы ............................................................................................................... 193
7.5. Сведения о сбоях при валидации данных ........................................................................... 197
Итоги ............................................................................................................................................. 202
Глава 8. Расширенный контроль конкурентности .............................................. 203
8.1. Сложность блокировок......................................................................................................... 203
8.2. Потокобезопасный счетчик с атомами ............................................................................... 205
8.3. Потокобезопасный кеш с атомами ...................................................................................... 211
8.4. Управление состоянием с помощью атомов ...................................................................... 213
Итоги ............................................................................................................................................. 215
Глава 9. Персистентные структуры данных ......................................................... 217
9.1. Потребность в персистентных структурах данных ............................................................ 217
9.2. Эффективность персистентных структур данных ............................................................. 221
9.3. Библиотеки персистентных структур данных .................................................................... 227
9.3.1. Персистентные структуры данных в Java ................................................................ 228
9.3.2. Персистентные структуры данных в JavaScript ...................................................... 229
9.4. Персистентные структуры данных в действии .................................................................. 232
9.4.1. Написание запросов с персистентными структурами данных ............................... 232
9.4.2. Операции изменения при работе с персистентными структурами данных .......... 235
9.4.3. Сериализация и десериализация ............................................................................... 236
9.4.4. Структурная разница ................................................................................................. 237
Итоги ............................................................................................................................................. 240
Глава 10. Операции с базой данных ........................................................................ 243
10.1. Извлечение данных из базы данных ................................................................................. 244
10.2. Хранение данных в базе данных ....................................................................................... 251
10.3. Простая манипуляция данными......................................................................................... 254
10.4. Продвинутая обработка данных ........................................................................................ 258
Итоги ............................................................................................................................................. 266
Глава 11. Веб-сервисы ................................................................................................ 269
11.1. Другой запрос функции ...................................................................................................... 270
11.2. Создание внутренностей, подобных внешностям ............................................................ 270
11.3. Представление запроса клиента в виде карты .................................................................. 273
11.4. Представление ответа сервера в виде карты .................................................................... 276
11.5. Дальнейшая передача информации ................................................................................... 281
11.6. Расширение результатов поиска в действии .................................................................... 284
11.7. Доставка в срок ................................................................................................................... 295
Итоги ............................................................................................................................................. 296
ЧАСТЬ 3. УДОБСТВО СОПРОВОЖДЕНИЯ ....................................................... 297
Глава 12. Расширенная проверка данных ............................................................. 299
12.1. Проверка аргументов функции .......................................................................................... 299
12.2. Проверка возвращаемого значения ................................................................................... 308
12.3. Расширенная проверка данных.......................................................................................... 310
12.4. Автоматическое создание диаграмм модели данных ...................................................... 314
8
Оглавление
12.5. Автоматическая генерация модульных тестов на основе схемы .................................... 316
12.6. Новый подарок .................................................................................................................... 324
Итоги ............................................................................................................................................. 326
Глава 13. Полиморфизм ............................................................................................. 327
13.1. Сущность полиморфизма ................................................................................................... 327
13.2. Мультиметоды с единичной отправкой ............................................................................ 332
13.3. Мультиметоды с множественной отправкой .................................................................... 337
13.4. Мультиметоды с динамической отправкой ...................................................................... 343
13.5. Интеграция мультиметодов: производство ...................................................................... 346
Итоги ............................................................................................................................................. 351
Глава 14. Расширенная обработка данных ............................................................ 353
14.1. Обновление значения на карте с помощью выразительности ........................................ 353
14.2. Манипулирование вложенными данными ........................................................................ 357
14.3. Использование наилучшего инструмента для работы ..................................................... 360
14.4. Легкое разматывание .......................................................................................................... 365
Итоги ............................................................................................................................................. 370
Глава 15. Отладка ....................................................................................................... 371
15.1. Детерминизм в программировании ................................................................................... 371
15.2. Репродуцируемость с числами и строками ...................................................................... 375
15.3. Репродуцируемость с любыми данными .......................................................................... 379
15.4. Модульные тесты ................................................................................................................ 383
15.5. Работа с внешними источниками данных ........................................................................ 392
Прощание ..................................................................................................................................... 394
Итоги ............................................................................................................................................. 394
Приложение A. Принципы дата-ориентированного программирования ........ 397
A.1. Принцип № 1: отделяйте код от данных ............................................................................ 398
A.1.1. Иллюстрация к принципу № 1 ................................................................................. 399
Нарушение принципа № 1 в ООП ................................................................................... 399
Нарушение принципа № 1 в ФП ..................................................................................... 400
Соблюдение принципа № 1 в ООП ................................................................................. 400
Соблюдение принципа № 1 в ФП ................................................................................... 401
A.1.2. Преимущества принципа № 1 .................................................................................. 401
Преимущество № 1: код может быть повторно использован в различных
контекстах ......................................................................................................................... 402
Преимущество № 2: код может быть протестирован изолированно ........................... 405
Преимущество № 3: системы, как правило, менее сложны .......................................... 406
A.1.3. Издержки принципа № 1 .......................................................................................... 408
Издержка № 1: отсутствие контроля над тем, какой код к каким данным
может получить доступ .................................................................................................... 408
Издержка № 2: отсутствие пакетирования ..................................................................... 408
Издержка № 3: системы состоят из большего количества объектов ........................... 409
A.1.4. Основная суть принципа № 1 ................................................................................... 409
A.2. Принцип № 2: представляйте данные с помощью обобщенных структур...................... 410
A.2.1. Иллюстрация к принципу № 2 ................................................................................. 410
A.2.2. Преимущества принципа № 2 .................................................................................. 411
Оглавление
9
Применение функций, которые не ограничены определенным случаем
использования ................................................................................................................... 412
Гибкая модель данных ..................................................................................................... 412
A.2.3. Издержки принципа № 2 .......................................................................................... 413
Издержка № 1: снижение производительности ............................................................. 413
Издержка № 2: отсутствие схемы данных ...................................................................... 414
Издержка № 3: отсутствует проверка данных на валидность
во время компиляции ....................................................................................................... 414
Издержка № 4: необходимость явного приведения типов ............................................ 415
A.2.4. Основная суть принципа № 2 ................................................................................... 416
A.3. Принцип № 3: данные неизменяемы .................................................................................. 417
A.3.1. Иллюстрация к принципу № 3 ................................................................................. 417
A.3.2. Преимущества принципа № 3 .................................................................................. 419
Преимущество № 1: надежный доступ к данным для всех ........................................... 419
Преимущество № 2: предсказуемое поведение кода ..................................................... 419
Преимущество № 3: быстрая проверка равенства ......................................................... 420
Преимущество № 4: безопасность конкурентности без затрат .................................... 420
A.3.3. Издержки принципа № 3 .......................................................................................... 420
Издержка № 1: снижается производительность ............................................................ 421
Издержка № 2: требуется библиотека для персистентных структур данных .............. 421
A.3.4. Основная суть принципа № 3 ................................................................................... 421
A.4. Принцип № 4: отделяйте схему данных от представления данных ................................. 422
A.4.1. Иллюстрация к принципу № 4 ................................................................................. 422
A.4.2. Преимущества принципа № 4 .................................................................................. 424
Преимущество № 1: возможность свободно выбирать, какие данные
следует валидировать ....................................................................................................... 424
Преимущество № 2: наличие опциональных полей ...................................................... 426
Преимущество № 3: наличие расширенных условий валидации данных .................... 428
Преимущество № 4: возможность автоматического создания визуализации
модели данных .................................................................................................................. 428
A.4.3. Издержки принципа № 4 .......................................................................................... 429
Издержка № 1: слабая связь между данными и их схемой ........................................... 430
Издержка № 2: небольшое снижение производительности .......................................... 430
A.4.4. Основная суть принципа № 4 ................................................................................... 430
Заключение ................................................................................................................................... 431
Приложение B. Обобщенный доступ к данным
в статически типизированных языках ................................................................... 433
B.1. Динамические геттеры для строковых карт ...................................................................... 433
B.1.1. Доступ к невложенным полям карты с помощью динамических геттеров .......... 434
B.1.2. Доступ к вложенным полям карты с помощью динамических геттеров.............. 435
B.2. Геттеры значений для карт .................................................................................................. 436
B.2.1. Доступ к невложенным полям карты с помощью геттеров значений .................. 437
B.2.2. Доступ к вложенным полям карты с помощью геттеров значений ...................... 438
B.3. Типизированные геттеры для карт ..................................................................................... 440
B.3.1. Доступ к невложенным полям карты с помощью типизированных геттеров ......... 440
B.3.2. Доступ к вложенным полям карты с помощью типизированных геттеров ......... 441
10
Оглавление
B.4. Обобщенный доступ к членам класса ................................................................................ 443
B.4.1. Обобщенный доступ к не вложенным членам класса ............................................ 443
B.4.2. Обобщенный доступ к членам вложенного класса ................................................ 447
B.4.3. Автоматическая сериализация объектов JSON ...................................................... 450
Итоги ............................................................................................................................................. 451
Приложение C. Дата-ориентированное программирование:
звено в цепи парадигм программирования ........................................................... 453
C.1. Хронология ........................................................................................................................... 453
C.1.1. 1958 год: Lisp ............................................................................................................. 453
C.1.2. 1981 год: значения и объекты .................................................................................. 453
C.1.3. 2000 год: идеальные хеш-деревья ............................................................................ 455
C.1.4. 2006 год: «Из ямы со смолой» ................................................................................. 455
C.1.5. 2007 год: Clojure ........................................................................................................ 455
C.1.6. 2009 год: неизменяемость для всех ......................................................................... 455
C.2. Принципы ДОП как наилучший подход ............................................................................ 456
C.2.1. Принцип № 1: отделяйте код от данных ................................................................. 456
C.2.2. Принцип № 2: представляйте данные с помощью обобщенных структур ........... 456
C.2.3. Принцип № 3: данные неизменяемы ....................................................................... 456
C.2.4. Принцип № 4: отделяйте схему данных от представления данных ...................... 457
C.3. ДОП и другие парадигмы, связанные с данными ............................................................. 458
C.3.1. Дата-ориентированная разработка ........................................................................... 458
C.3.2. Дата-управляемое программирование ..................................................................... 458
C.3.3. Дата-ориентированное программирование (ДОП) ................................................. 459
Итоги ............................................................................................................................................. 459
Приложение D. Ссылки на Lodash........................................................................... 461
Карин,
которая ежедневно за мной присматривает
Вступительное слово
Каждый принцип программирования, каждый метод проектирования, каждый ар-хитектурный стиль и даже большинство языковых функций связаны с организаци-ей сложности и возможностью адаптации. Две характеристики — неизменяемые
данные и превращение частей программы в данные внутри самой программы —
привлекли меня к Clojure в 2009 году, а совсем недавно к дата-ориентированному
программированию Йехонатана Шарвита.
В 2005 году я работал над одним из моих любимых проектов с некоторыми из моих
любимых людей. Это был проект Java, но мы сделали две вещи, которые в то время
не были распространены в мире Java. Во-первых, мы сделали наши основные значения данных неизменяемыми. Это было непросто, но сработало необыкновенно
хорошо. Мы вручную накрутили методы clone и deepClone во многих классах. От-дача была огромной. В качестве примера предположим, что вам нужны шаблонные
документы для создания экземпляров пользователями. Когда вы можете делать копии целых деревьев объектов, самим объектам не нужно «знать», являются ли они
данными шаблона или данными экземпляра. Это решение зависит от того, какой
объект содержит ссылку. Еще одно большое преимущество было получено от сравнения: когда значения неизменны, равенство идентичности указывает на равенство
значений. Это может привести к очень быстрой проверке на равенство.
Наш второй метод заключался в том, чтобы использовать общие данные, хотя и не
в той степени, в какой Йехонатан покажет вам в этой книге. Там, где один слой
имел иерархию классов, соседний слой представлял бы их как экземпляры более
общего класса. То, что было бы переменной-членом в одном слое, будет описано
полем в словаре в другом слое. Я уверен, что на этот стиль повлияли несколько
болтунов в нашей команде. Это также сразу же окупилось, так как мы смогли компоновать и перекомпоновывать объекты в разных конфигурациях.
Дата-ориентированное программирование, как вы увидите, обещает уменьшить
случайную сложность и повысить уровень абстракции, с которым вы работаете. Вы
начнете рассматривать повторяющееся поведение в своих программах как искусст-венное, результат разделения общих функций на классы, которые действуют как
маленькие пространства имен, которые работают только с подмножеством значений вашей программы (их экземплярами). Мы можем «сложить вместе» почти все
14 Вступительное
слово
эти значения в карты и списки. Мы можем превратить имена участников (данные
с трудом доступны через рефлексивные API) в ключи карты. Когда мы это делаем, код просто тает. Это первый уровень просветления.
На этом этапе вы можете возразить, что компилятор использует эти имена членов
во время компиляции для проверки правильности. Действительно так. Но верьте, потому что Йехонатан проведет вас к следующему уровню понимания: эти проверки во время компиляции являются небольшим подмножеством возможных проверок правильности значений. Мы также можем превратить проверки правильности
в данные! Мы можем преобразовать схемы в значения внутри наших программ.
Более того, мы можем применять критерии, которые все еще пытаются выяснить
исследователи систем типов. Это второй уровень просветления.
Дата-ориентированное программирование особенно ярко проявляется при работе
с веб-API. В проводной сети нет типа системы, поэтому попытка сопоставить по-лезную нагрузку запроса непосредственно с классом предметной области гарантирует ненадежную и сложную реализацию. Если мы позволим данным быть данными, мы получим более простой код и гораздо меньше зависимостей от стомега-байтных фреймворковых библиотек.
Итак, что случилось с такими достоинствами ООП, как инкапсуляция, наследование и полиморфизм? Оказывается, мы можем разобрать их и получить каждый из
них по меню. (По моему мнению, наследование реализаций является наименее
важным из них, хотя его часто преподают в первую очередь. Теперь я предпочитаю
наследование интерфейсов через протоколы и сигнатуры общих функций.) Дата-ориентированное программирование предлагает полиморфизм «традиционного»
типа: отправка одной из многих функций на основе типа первого аргумента
(в объектно-ориентированном языке это маскировка первого аргумента метода.
Просто бывает, что он стоит перед «.»). Однако, как и в случае проверки схемы, ДОП допускает больший динамизм. Представьте диспетчеризацию на основе типов
первых двух аргументов. Или на основе того, есть ли в аргументе поле «день рождения» с сегодняшней датой! Это третий уровень просветления.
А что касается инкапсуляции, мы все равно должны применить ее к организующей
логике нашей программы. Мы инкапсулируем подсистемы, а не значения. Эта инкапсуляция воплощает сокрытие решений Дэвида Парнаса. Внутри подсистемы мы
можем перестать отгораживать наши данные стеной от разрозненных пространств
имен, навязываемых классами. По словам Алана Перлиса, «лучше иметь сто функций, работающих с одной структурой данных, чем десять функций с десятью
структурами данных».
В нашей бесконечной битве с энтропией мы можем использовать дата-ориентированное программирование, чтобы уменьшить объем кода, чтобы не отставать и
повысить уровень абстракции, чтобы сделать логику и смысл нашей программы
точными и очевидными. Наслаждайтесь путешествием и останавливайтесь на каждом новом плато, чтобы насладиться видом и сказать себе: «Это просто данные!»
Майкл Т. Нигард,
автор книги «Release It!: Design and Deploy Production-Ready Software»
Вступительное слово
15
Эта книга поразила меня в нужное время. Я создавал веб-приложения почти 20 лет
в объектно-ориентированной среде. Я никогда не считал себя опытным программистом, но я достаточно хорошо знал свои инструменты, чтобы рассмотреть типич-ную бизнес-задачу, набросать модель данных и создать приложение в стиле MVC, чтобы выполнить свою работу.
Проекты были захватывающими в начале. Мне нравилось ощущение соединения
частей воедино и наблюдение за тем, как приложение оживает. Но как только я заставил его работать, я столкнулся с проблемами. Я не мог изменить одну деталь, не
имея в виду все остальные модели. Я знал, что должен писать тесты, но мне нужно
было настроить так много состояний для тестирования вещей, что оно того не
стоило: я не хотел писать больше кода, который было бы трудно изменить. Даже
запуск фрагментов кода в консоли был утомительным, потому что мне приходи-лось создавать состояние базы данных для вызова метода. Я думал, что, вероятно, делаю это неправильно, но решения, о которых я знал, такие как сложные среды
тестирования, казалось, усложняли, а не облегчали задачу.
Затем однажды я увидел на YouTube выступление Рича Хики, создателя Clojure. Он
объяснял функциональное программирование и противопоставлял его объектно-ориентированному программированию, которое он насмешливо называл «про-странственно-ориентированным программированием». Я не был уверен, прав ли
он, но услышал скрытое сообщение, которое меня заинтриговало: «Дело не в тебе, дело в твоем языке». Я посмотрел все видео, которые смог найти, и начал думать, что Clojure может быть ответом.
Прошли годы. Я продолжал смотреть видеоролики о Clojure и пытался применять
функциональные принципы, когда мог. Но всякий раз, когда приходило время начинать новый проект, я возвращался к своему знакомому фреймворку. Переход на
другой язык с совершенно другой экосистемой библиотек был слишком большим
скачком.
Затем, когда я собирался начать работу над новым продуктом, я нашел эту книгу.
Слова «дата-ориентированное» в заголовке прозвучали звоночком. Я слышал, как
программисты в тех видеороликах Clojure использовали эти слова раньше, но я
действительно не понимал, что они означают. Кое-что о том, как проще создавать
системы, которые манипулируют литералами данных (например, картами и массивами) вместо пользовательских объектов. Языки, которые я знал, хорошо поддерживали литералы данных, поэтому я подумал, что могу научиться чему-то, что поможет мне продержаться до того волшебного дня, когда я смогу переключиться на
Clojure.
Мое первое озарение произошло прямо во вступлении. На первых нескольких
страницах Йехонатан объясняет, что, хотя он пишет на Clojure уже 10 лет, книга не
привязана к конкретному языку, а примеры будут на JavaScript. «Подожди! — подумал я. — Неужели мне не нужно менять языки, чтобы значительно улучшить
способ написания программ?»
Я был так взволнован этой перспективой, что проглотил книгу в один присест. Мои
глаза открылись и увидели то, что все это время было прямо передо мной. Конечно,
16 Вступительное
слово
мой код было трудно протестировать! Из-за ORM, который я использовал, вся моя
функциональность была написана в объектах, которые предполагали множество
состояний базы данных! Когда я увидел, как это прописано с примерами в книге, я не мог развидеть это. Мне не нужен был новый язык, мне просто нужно было
по-другому подойти к программированию!
Все разработчики, которых я считаю великими, указывают на одно и то же: хорошая разработка заключается в разделении вещей. Дело не только в том, чтобы заставить код работать, каким бы уродливым он ни был. Речь идет о распутывании
частей друг от друга, чтобы вы могли изменить одну вещь, не нарушая все осталь-ное.
В этой книге код и данные разбираются на части с неожиданными и захватывающими результатами. Шарвит отделил способ программирования от определенного
языка. Возможно, я никогда не перейду на Clojure, да и не чувствую, что мне это
нужно. Дата-ориентированное программирование помогло мне увидеть новые возможности языков, которые я знаю, и множество новых фреймворков, появляющих-ся каждый день.
Райан Сингер,
автор книги «Shape Up: Stop Running in Circles and Ship Work that Matters»
Введение
Я работаю инженером-программистом с 2000 года. Для меня очевидно, что есть
«до» и «после» 2012 года. Почему именно 2012 год? Потому что 2012 год — это
год, когда я открыл для себя Clojure. До Clojure программирование было моей ра-ботой. После Clojure программирование стало моей страстью.
Несколько лет назад я задавался вопросом, какие особенности Clojure сделали этот
язык программирования таким большим источником удовольствия для меня. Я поделился своими вопросами с другими членами сообщества Clojure, которые испы-тывают к нему ту же страсть, что и я. Вместе мы обнаружили, что особенным в Clojure были не функции, а принципы.
Когда мы решили выделить основные принципы Clojure, мы поняли, что они, по
сути, применимы и к другим языкам программирования. Именно тогда начала за-рождаться идея этой книги. Я хотел поделиться тем, что мне так нравится в Clojure, с мировым сообществом разработчиков. Для этого мне понадобилось бы средство
четкого выражения идей, которые в основном не знакомы разработчикам, не знаю-щим Clojure.
Мне всегда нравилось придумывать истории, но будут ли программисты воспринимать мои придуманные диалоги всерьез? Конечно, Платон придумывал истории
в сократических диалогах, чтобы передать учение своего учителя. Точно так же
раввин Иуда Халеви придумал историю о царе хазар, чтобы объяснить основы
иудаизма. Но эти две работы относятся к области мысли, а не практики!
Затем я вспомнил книгу по менеджменту, которую прочитал несколько лет назад, под названием «Цель» (North River Press, 2014). В этой книге Элияху Голдратт рассказывает историю директора завода, которому удается спасти свою фабрику благодаря принципам, вытекающим из теории ограничений. Платон, Иуда Халеви и
Элияху Голдратт узаконили мое безумное желание написать историю, чтобы поделиться идеями.
18
Введение
Благодарности
Прежде всего, я хочу поблагодарить мою возлюбленную Карин. Ты верила в меня
с самого начала этого проекта. Тебе всегда удается увидеть свет, даже когда он
скрывается за несколькими слоями тьмы. Моим замечательным детям — Одайе, Орлу, Адваху, Нехораю и Яиру, которые были первыми слушателями историй, которые я придумал, когда был молодым папой. Вы самая прекрасная история, которую я когда-либо писал!
Есть много других людей, которым я также хочу выразить свою благодарность, в том числе Джоэлу Кляйну за все увлекательные и обогащающие дискуссии об
искусстве и душе; Меир Армон за то, что помогла мне уточнить, какой контент не
следует включать в книгу; Ричу Хикки за изобретение Clojure, такого прекрасного
языка, который охватил дата-ориентированное программирование еще до того, как
у него появилось название; Кристофу Гранду, чьи ценные советы помогли мне выделить первые три принципа дата-ориентированного программирования; Марку
Шампайну за тщательный анализ рукописи и множество ценных предложений; Эрику Норманду за поддержку и в частности советы по применению дата-ориентированного программирования в Java; Берту Бейтсу за то, что научил меня секре-там написания хорошей книги, и Бену Баттону за ознакомление с главами, посвя-щенными схеме JSON.
Моя благодарность всем сотрудникам издательства Manning Publications, особенно
Майку Стивенсу, за согласие продолжать работать со мной, несмотря на провал
моей первой книги; Элеше Хайд за доступность и внимание к мельчайшим деталям; Мариусу Бутуку за восторженные положительные отзывы от прочтения первой главы; Линде Котлярски за то, что описания глав были составлены в такой
увлекательной форме, и Фрэнсис Буран за улучшение ясности текста и истории.
Всем рецензентам: Алексу Гауту, Аллену Дингу, Андреасу Шабусу, Эндрю Джен-нингсу, Энди Киршу, Энн Эпштейн, Бертольду Франку, Кристиану Крейцер-Беку, Кристоферу Карделлу, Дейну Балии, доктору Давиду Кадамуро, Элиасу Илмари
Лиинамаа, Эзре Симелоффу, Джорджу Томасу, С. Гири, Джулиано Араужо Берто-ти, Грегору Рейману, Дж. М. Боровина Йоско, Джероми Мейеру, Хесусу А. Хуаре-су Герреро, Джону Д. Льюису, Джону Гюнтеру, Келуму Прабату Сенанаяке, Кел-вину Джонсону, Кенту Р. Спиллнеру, Ким Габриэлсен, Константину Еремину, Маркусу Гезелле, Марку Элстону, Мэтью Проктору, Маурицио Томази, Майклу
Айдинбасу, Милораду Имбра, Озайу Думану, Раффаэллу Вентальо, Раманану На-рараджану, Рамбабу Поза, Саурабху Сингху, Сету Макферсону, Шайло Моррис, Виктору Дюрану, Винсенту Терону, Уильяму Э. Уилеру, Йогешу Шетти и Ивану
Фелизоту, ваши предложения помогли сделать эту книгу лучше.
Наконец, я хотел бы упомянуть моего двоюродного брата Ниссима, которому банда
варваров не позволила процветать.
Введение
19
Об этой книге
Дата-ориентированное программирование было написано для того, чтобы помочь
разработчикам снизить сложность создаваемых ими систем. Идеи, изложенные
в этой книге, в основном применимы к системам, которые управляют информацией, — таким как интерфейсные приложения, внутренние веб-серверы или веб-службы.
Кто должен прочитать эту книгу?
Дата-ориентированное программирование предназначено для разработчиков fron-tend, backend и full stack с парой лет опыта работы на языках программирования
высокого уровня, таких как Java, C#, C++, Ruby или Python. Для разработчиков
объектно-ориентированного программирования некоторые идеи, представленные
в этой книге, могут вывести их из зоны комфорта и потребовать от них отучиться
от некоторых парадигм программирования, с которыми они чувствуют себя легко.
Разработчикам функционального программирования эту книгу будет немного легче
переварить, но она также преподнесет несколько приятных сюрпризов.
Как организована эта книга: дорожная карта
В этой книге рассказывается история, иллюстрирующая дата-ориентированное программирование (ДОП) и то, как применять его принципы в реальных производст-венных системах. Мое предложение состоит в том, чтобы следить за историей и
читать главы по порядку. Однако, если некоторые главы вызывают у вас большее
любопытство, чем другие, имейте в виду, что материал из части 1 и главы 7 необходим для понимания частей 2 и 3.
На протяжении всей книги мы используем Lodash (https://lodash.com /), чтобы
проиллюстрировать, как манипулировать данными с помощью универсальных
функций. В случае если вы читаете фрагмент кода, который использует незнако-мую вам функцию Lodash, вы можете обратиться к приложению D, чтобы понять
поведение функции.
Часть 1 «Гибкость» содержит шесть глав, освещает проблемы традиционного
объектно-ориентированного программирования (ООП) и ставит в центр внимания
дата-ориентированное программирование, показывая, как создавать гибкие системы, используя основные принципы ДОП. Главы выстраиваются таким образом: В главе 1 «Сложность объектно-ориентированного программирования» мы
рассмотрим сложность ООП. Тогда начинается наша ДОП-сага! Послушайте
разговор между Тео, старшим разработчиком, и его многообещающим коллегой
Дейвом. Почувствуйте сочувствие к Тео, борющемуся со сложностью ООП, и найдите отличную причину для того, чтобы попробовать другую парадигму
программирования.
В главе 2 «Разделение кода и данных» наш друг Тео ищет решение, которое
уменьшит сложность и повысит гибкость систем. На кону его работа. Входит
20
Введение
Джо, опытный разработчик, у которого есть для него ответ — ДОП. Узнайте, как принцип ДОП № 1 помогает снизить сложность информационных систем.
В главе 3 «Основы манипулирования данными» исследуется, как мы можем освободить наши данные от инкапсуляции в жесткость класса и свободно манипулировать ими с помощью универсальных функций, применяя принцип ДОП № 2. Да
здравствует революция!
Глава 4 «Управление состоянием» исследует управление состоянием с помощью
мультиверсионного подхода, который позволяет нам вернуться назад во времени, восстановив систему до предыдущего состояния, потому что в ДOП состояние — это не что иное, как данные. Путешествие во времени реально — в ДОП!
Глава 5 «Основы управления параллелизмом» помогает нам получить высокую
пропускную способность операций чтения и записи в параллельной системе, применяя оптимистичную стратегию управления параллелизмом. Розовые очки
не требуются!
Глава 6 «Модульные тесты» предлагает выпить чашечку кофе... с Джо! Наш
друг Джо доказывает, что модульное тестирование кода, ориентированного на
данные, настолько просто, что вы можете заняться им в кафе. Возьмите чашечку
кофе и узнайте, почему это так просто (даже для изменений!), когда вы пишете
модульный тест ДОП вместе с Джо. Это по-настоящему круто!
Часть 2 «Масштабируемость» иллюстрирует, как создать масштабируемую систему ДОП с акцентом на проверку данных, многопоточные среды, большие коллекции данных, а также доступ к базам данных и веб-службам. Вам нужно увели-чить размер вашей системы? Нет проблем!
Глава 7 «Базовая проверка данных» учит нас, как убедиться, что данные, поступающие в наши системы и из них, являются достоверными, на всякий случай...
потому что, как говорит Джо, вас не заставляют проверять данные в ДОП, но вы
можете, когда вам нужно. Проверять или не проверять — вот в чем вопрос!
В главе 8 «Расширенное управление параллелизмом» обсуждается, что после того
как наш друг Джо разберет детали реализации механизма «атом», мы научимся
управлять всем состоянием системы потокобезопасным способом без использования блокировок. Вы не узнаете сложность от атома до атома!
Глава 9 «Постоянные структуры данных» переносится в более академическую
среду, где наш друг Джо раскрывает внутренние детали более безопасного и
масштабируемого способа сохранения неизменности данных, а также того, как
его эффективно реализовать независимо от размера данных. Урок уже начался!
Глава 10 «Операции с базами данных» учит нас, как представлять данные из ба-зы данных, получать к ним доступ и управлять ими таким образом, чтобы обеспечить дополнительную гибкость и (как вы уже догадались) меньшую сложность.
Глава 11 «Веб-службы» позволяет нам открыть для себя простоту взаимодействия с веб-службами. Мы узнаем, что Джо имеет в виду, когда говорит: «Мы
Введение
21
должны строить внутреннюю часть наших систем так же, как мы строим внешнюю».
Часть 3 «Поддерживаемость» включает в себя такие методы ДОП, как расширенная проверка данных, полиморфизм, красноречивый код и методы отладки, которые жизненно важны, когда вы работаете в команде. Добро пожаловать в команду!
Глава 12 «Расширенная проверка данных» позволяет нам определить форму бу-дущих событий. Здесь вы узнаете, как проверять данные, когда они поступают
внутри системы, что позволяет упростить разработку, определяя ожидаемую
форму аргументов функции и возвращаемых значений.
Глава 13 «Полиморфизм» переносит нас вместе с Тео и Дейвом на занятия
в сельской местности — подходящее место, чтобы поиграть с животными
и узнать о полиморфизме без объектов с помощью мультиметодов.
Глава 14 «Расширенные возможности обработки данных» позволяет нам увидеть, как Дейв и Тео применяют мудрый совет Джо, чтобы превратить утомительный код в красноречивый код, создавая свои собственные инструменты для
обработки данных. «Поставь телегу впереди лошади» — это еще одна жемчу-жина от Джо!
Глава 15 «Отладка» приводит Дэйва и Тео в музей, чтобы в последний раз сказать «ура», поскольку они создают инновационное решение для воспроизведения и исправления ошибок.
Эта книга также имеет четыре приложения.
Приложение А «Принципы дата-ориентированного программирования» обобщает каждый из четырех принципов ДОП, которые подробно рассматриваются
в части 1, и иллюстрирует, как каждый принцип может быть применен как
к языкам FP, так и к ООП. В нем также описываются преимущества каждого
принципа и затраты на соблюдение каждого из них.
В приложении В «Общий доступ к данным в статически типизированных языках» представлены различные способы обеспечения общего доступа к данным
в статически типизированных языках программирования, таких как Java и C#.
Приложение C «Дата-ориентированное программирование: звено в цепи парадигм программирования» исследует идеи и тенденции, которые вдохновили
ДОП. Мы рассматриваем открытия, которые делают его применимым в произ-водственных системах в больших масштабах.
Приложение D «Ссылки на Lodash» содержит краткое описание функций
Lodash, которые мы используем на протяжении всей книги, чтобы проиллюстрировать, как манипулировать данными с помощью универсальных функций, не
изменяя их.
О коде
Большинство фрагментов кода в этой книге написаны на JavaScript. Мы выбрали
JavaScript по двум причинам:
22
Введение
JavaScript поддерживает как функциональное программирование, так и объектно-ориентированный стиль программирования.
Синтаксис JavaScript легко читается в том смысле, что даже если вы не знакомы
с JavaScript, вы можете прочитать фрагмент кода JavaScript на высоком уровне, как
если бы это был псевдокод.
Чтобы читателям с любого языка программирования было легко читать фрагменты
кода, мы ограничились базовым синтаксисом JavaScript и избежали использования
расширенных языковых функций, таких как функции со стрелками и асинхронная
нотация. Там, где возникла концептуальная проблема в применении идеи к статически типизированному языку, мы добавили фрагменты кода на Java.
Код отображается по всему тексту и в виде отдельных фрагментов кода шрифтом
фиксированной ширины, подобным этому. Во многих случаях исходный код был
переформатирован. Мы добавили разрывы строк и переработали отступы, чтобы
вместить доступное пространство страницы в книге. Аннотации к коду также со-провождают некоторые списки, выделяя важные концепции.
Вы можете получить исполняемые фрагменты кода из онлайн-версии этой книги
в liveBook по адресу https://livebook.manning.com/book/data-oriented-programming, или по ссылке на Github книги здесь: https://github.com/viebel/data-oriented-programming.
Дискуссионный форум liveBook
Покупка «Дата-ориентированного программирования» включает бесплатный доступ к liveBook, онлайн-платформе для чтения издательства «Мэннинг». Используя
эксклюзивные функции обсуждения liveBook, вы можете прикреплять комментарии
к книге глобально или к определенным разделам или абзацам. Легко делать замет-ки, задавать технические вопросы и отвечать на них, а также получать помощь от
автора и других пользователей. Чтобы получить доступ к форуму, перейдите по
ссылке https://livebook.manning.com/book/data-Oriented-programming/discussion.
Вы также можете узнать больше о форумах издательства «Мэннинг» и правилах
поведения на https://livebook.manning.com/discussion.
Обязательство издательства «Мэннинг» перед читателями состоит в том, чтобы
предоставить место, где может состояться содержательный диалог между отдельными читателями и между читателями и автором. Это не обязательство какой-либо
конкретной суммы участия со стороны автора, чей вклад в форум остается добро-вольным (и неоплачиваемым). Мы предлагаем вам попробовать задать автору
несколько сложных вопросов, чтобы его интерес не потерялся! Форум и архивы
предыдущих обсуждений будут доступны на веб-сайте издателя, пока книга находится в печати.

Введение
23
Об авторе
Йехонатан Шарвит имеет более чем 20-летний опыт работы
в качестве инженера-программиста, программируя на C++,
Java, Ruby, JavaScript, Clojure и ClojureScript, как в бэкенде, так
и во внешнем интерфейсе. На момент написания этой книги он
работал архитектором программного обеспечения в Cyclognito,
создавая программные инфраструктуры для крупномасштаб-
ных конвейеров данных. Он делится своей страстью к про-
граммированию в своем блоге (https://blog.klipse.tech/) и на
технических конференциях. Вы можете следить за ним в «Твиттере»:
https://twitter.com/viebel.
Об иллюстрации на обложке
Рисунок на обложке «Fille de l'Isle Santorin», или «Девушка с острова Санторини», взят из сборника Жака Грассе де Сен-Совера, опубликованного в 1797 году. Каждая
иллюстрация там нарисована и раскрашена вручную. В те дни было легко определить, где живут люди и каково их ремесло или положение в жизни, просто по их
одежде. На своих обложках издательство Manning подчеркивает творческую жилку
и инициативность, присущие всем, кто занят в сфере IT. Хочется напомнить, как
разнообразны были региональные культуры еще пару веков назад и возродить
память о тех временах, украшая книги картинками из подобных этнографических
коллекций.
Персонажи пьесы
ТЕО, старший разработчик
НЭНСИ, предпринимательница
МОНИКА, менеджер, босс Тео
ДЕЙВ, младший разработчик, коллега Тео
ДЖО, независимый программист
КЕЙ, психотерапевт, жена Джо
ДЖЕЙН, жена Тео
НЕРАЙЯ, сын Джо
АУРЕЛИЯ, дочь Джо
Действие этой истории происходит в Сан-Франциско.
24
Введение
Часть 1
Гибкость
Утро понедельника. Теодор сидит с Нэнси на террасе La Vita è Bella, итальян-ской кофейни рядом с зоопарком Сан-Франциско. Нэнси — предпринимательница, которая ищет агентство по развитию для своей стартап-компании Klafim. Тео работает в агентстве по разработке программного обеспечения Albatross, которое стремится вернуть доверие стартапов.
Нэнси и ее партнер по бизнесу собрали начальный капитал для Klafim, социальной
сети для книг. Уникальное ценностное предложение Klafim заключается в объединении онлайн-мира с физическим миром, позволяя пользователям брать книги из
местных библиотек, а затем встречаться онлайн для обсуждения книг. Большая
часть продукта основана на интеграции существующих онлайн-сервисов. Единственное, что требует разработки программного обеспечения, — это то, что Нэнси
называет глобальной системой управления библиотеками. Их беседу на мгновение
прерывает официант, который приносит Тео его крепкий эспрессо, а Нэнси амери-кано с молоком.
Тео: По-твоему, что такое глобальная система управления библиотеками?
Нэнси: Это программная система, которая выполняет основные функции ведения
библиотечных дел, в основном связанные с каталогом книг и читателями.
Тео: Не могла бы ты немного конкретизировать?
Нэнси: Конечно. На данный момент нам нужен быстрый прототип. Если реакция
рынка на Klafim будет положительной, мы будем продвигаться вперед уже
с большим проектом.
Тео: Какие функции тебе нужны для этапа создания прототипа?
Нэнси берет салфетку из-под кофейной кружки и пишет на ней пару маркирован-ных пунктов.
ТРЕБОВАНИЯ К ПРОТОТИПУ KLAFIM
Два типа пользователей библиотеки — это читатели и библиотекари.
Пользователи входят в систему с помощью электронной почты и пароля.
Читатели могут брать книги напрокат.
26
Часть 1. Гибкость
Читатели и библиотекари могут искать книги по названию или по автору.
Библиотекари могут блокировать и разблокировать читателей (например, когда они
запаздывают с возвратом книги).
Библиотекари могут перечислить книги, которые в настоящее время предоставлены
читателям.
У книги может быть несколько экземпляров.
Книга принадлежит физической библиотеке.
Тео: Что ж, все это довольно понятно.
Нэнси: Сколько времени потребуется вашей компании, чтобы сделать прототип?
Тео: Я думаю, мы могли бы сделать его в течение месяца. Скажем, в среду, 30-го.
Нэнси: Это слишком долго. Нам он нужен через две недели!
Тео: Это тяжело! Можешь ли ты вырезать одну или две функции?
Нэнси: К сожалению, я не могу сократить какие-либо функции, но если хочешь, ты
можешь сделать поиск очень простым.
(Тео действительно не хочет терять этот контракт, поэтому он готов усердно работать и ложиться спать позже.)
Тео: Я думаю, что это может быть выполнено к среде 16-го.
Нэнси: Супер!
Сложность объектно-ориентированного
программирования
Капризный предприниматель
В ЭТОЙ ГЛАВЕ РАССМАТРИВАЮТСЯ
Тенденция ООП к увеличению сложности системы.
Что делает ООП-системы трудными для понимания.
Стоимость смешивания кода и данных в объекты.
В этой главе мы рассмотрим, почему системы объектно-ориентированного программирования (ООП), как правило, сложны. Эта сложность не связана с синтаксисом или семантикой конкретного языка ООП. Это то, что присуще фундаменталь-ному пониманию ООП: программы должны состоять из объектов, которые состоят
из некоторого состояния, вместе с методами для доступа к этому состоянию и
управления им.
На протяжении многих лет экосистемы ООП облегчали эту сложность, добавляя
новые функции в язык (например, анонимные классы и анонимные функции) и раз-рабатывая фреймворки, которые скрывают часть этой сложности, предоставляя более простой интерфейс для разработчиков (например, Spring и Jackson в Java).
Внутри фреймворки полагаются на расширенные возможности языка, такие как
отражение и пользовательские аннотации.
Эта глава не предназначена для прочтения как критический анализ ООП. Ее
цель — повысить вашу осведомленность о тенденции к усложнению ООП как парадигмы программирования. Надеюсь, это побудит вас открыть для себя другую
парадигму программирования, в которой сложность системы имеет тенденцию
к снижению. Эта парадигма известна как дата-ориентированное программирование (ДОП).
28
Часть 1. Гибкость
1.1. Дизайн ООП:
классический или традиционный?
ПРИМЕЧАНИЕ. Тео, Нэнси и их новый проект были представлены в начале первой части.
Прочитайте вступительную часть, если вы ее пропустили.
Тео возвращается в офис с салфеткой Нэнси в кармане и с большим беспокойством
в душе, потому что он знает, что поставил себе жесткий дедлайн. Но у него не было
выбора! На прошлой неделе Моника, его босс, совершенно ясно сказала ему, что он
должен заключить сделку с Нэнси, несмотря ни на что.
Albatross, где работает Тео, — консалтинговая компания по программному обеспе-чению, имеющая клиентов по всему миру. Изначально у него было много клиентов
среди стартапов. Однако за последний год многие проекты плохо управлялись, и отдел стартапов потерял доверие своих клиентов. Вот почему руководство пере-вело Тео из корпоративного отдела в отдел стартапов в качестве старшего техниче-ского руководителя. Его работа заключается в том, чтобы заключать сделки и выполнять их вовремя.
1.1.1. Этап проектирования
Прежде чем броситься к своему ноутбуку, чтобы закодить систему, Тео берет лист
бумаги, намного больше салфетки, и начинает рисовать UML-диаграмму классов
системы, которая будет реализовывать прототип Klafim. Тео объектно-ориентированный программист. Здесь для него нет никаких вопросов — каждая бизнес-сущность представлена объектом, а каждый объект сделан из класса.
ТРЕБОВАНИЯ К ПРОТОТИПУ KLAFIM
Существуют два типа пользователей: читатели1 и библиотекари.
Пользователи входят в систему с помощью электронной почты и пароля.
Читатели могут брать книги напрокат.
Читатели и библиотекари могут искать книги по названию или по автору.
Библиотекари могут блокировать и разблокировать читателей (например, когда они
запаздывают с возвратом книги).
Библиотекари могут составить список книг, которые в настоящее время предоставлены читателю.
У книги может быть несколько экземпляров.
Книга принадлежит физической библиотеке.
Тео тратит некоторое время на размышления об организации системы. Он выделяет
основные классы для глобальной системы управления библиотеками Klafim.
1 Английское слово Member переведено как «читатель» в контексте системы библиотеки; в листингах представлен как member.
Глава 1. Сложность объектно-ориентированного программирования
29
ОСНОВНЫЕ КЛАССЫ СИСТЕМЫ УПРАВЛЕНИЯ БИБЛИОТЕКОЙ
Library — центральная часть системного проектирования.
Book — книга.
BookItem — книга может иметь несколько копий, и каждая копия рассматривается как
элемент книги.
BookLending — при предоставлении книги создается объект предоставления книги.
Member — читатель.
Librarian — библиотекарь.
User — базовый класс для библиотекаря и читателя.
Catalog — содержит список книг.
Author — автор книги.
Это была самая легкая часть. Теперь начинается самое сложное: отношения между
классами. Примерно через два часа Тео предлагает первый набросок проекта глобальной системы управления библиотеками. Это выглядит как схема на рис. 1.1.
ПРИМЕЧАНИЕ. Представленный здесь дизайн не претендует на звание самого умного
дизайна ООП: опытные разработчики ООП, вероятно, использовали бы пару шаблонов проектирования, чтобы предложить гораздо лучший дизайн. Этот дизайн должен быть наивным
и ни в коем случае не охватывает все функции системы. Это служит двум целям: для Тео, разработчика, он достаточен, чтобы начать кодить;
для меня, автора книги, он достаточен, чтобы проиллюстрировать сложность типичной
системы ООП.
Тео гордится собой и только что созданной им дизайнерской схемой. Он определенно заслужил чашечку кофе!
Возле кофемашины Тео знакомится с Дейвом, младшим разработчиком программного обеспечения, который присоединился к Albatross пару недель назад. Тео и
Дейв ценят друг друга, поскольку любопытство Дейва заставляет его задавать
сложные вопросы. Встречи у кофемашины часто превращаются в интересные дискуссии о программировании.
Тео: Привет, Дейв. Как день?
Дейв: Сегодня? Не очень. Я пытаюсь исправить ошибку в своем коде, но не могу
понять, почему состояние моих объектов всегда меняется. Но я уверен, что раз-берусь с этим. А как проходит твой день?
Тео: Я только что закончил дизайн системы для нового клиента.
Дейв: Круто! Можно ли мне посмотреть? Я пытаюсь улучшить свои дизайнерские
навыки.
Тео: Конечно! Схема у меня на столе. Если хочешь, мы можем взглянуть на нее
прямо сейчас.



30
Часть 1. Гибкость
Рис. 1.1. Диаграмма классов для глобальной системы управления библиотеками Klafim 1.1.2. UML 101
С латте в руке Дейв следует за Тео к его столу. Тео с гордостью показывает Дейву
свое произведение искусства: UML-диаграмму для системы управления библиотекой (см. рис. 1.1). Дейв, кажется, очень взволнован.
Дейв: Ух ты! Такая подробная диаграмма классов.
Тео: Да. Я очень доволен этим.
Дейв: Но дело в том, что я никогда не могу вспомнить значение разных стрелок.



Глава 1. Сложность объектно-ориентированного программирования
31
Тео: На моей диаграмме классов есть четыре типа стрелок: композиция, ассоциация, наследование и использование.
Дейв: В чем разница между композицией и ассоциацией?
ПРИМЕЧАНИЕ. Не волнуйтесь, если вы не знакомы с жаргоном ООП. Мы оставим это для
следующей главы.
Тео: Все дело в том, могут ли объекты жить друг без друга. В случае с композицией, когда умирает один объект, умирает и другой. Находясь в ассоциативном отношении, каждый объект имеет независимую жизнь.
СОВЕТ. В отношении композиции, когда умирает один объект, умирает и другой. Находясь в ассоциативном отношении, каждый объект имеет независимый жизненный цикл.
На диаграмме классов есть два типа композиций, обозначенных стрелкой с простым ромбом на одном краю и необязательной звездой на другом краю. На рис. 1.2
показано соотношение между:
Library, которой принадлежит Catalog, — композиция «один к одному». Если объект Library умирает, то вместе с ним умирает и его объект Catalog; Library, которая владеет многими Members, — это композиция «один ко многим».
Если объект Library умирает, то все его объекты Members умирают вместе с ним.
Рис. 1.2. Два вида композиции: «один к одному» и «один ко многим».
В обоих случаях, когда объект умирает, составной объект умирает вместе с ним
СОВЕТ. Композиционное соотношение представлено простым ромбом на одном краю и
необязательной звездой на другом краю.
Дейв: Есть ли ассоциативные связи на твоей диаграмме?
Тео: Взгляни на стрелку между Book и Author. У нее есть пустой ромб и звезда по
обоим краям, так что это ассоциативное отношение «многие ко многим».
Книга может быть написана несколькими авторами, и один автор может написать
несколько книг. Более того, объекты Book и Author могут жить независимо. Связь
между книгами и авторами представляет собой ассоциацию «многие ко многим»
(рис. 1.3).






32
Часть 1. Гибкость
СОВЕТ. Ассоциативное отношение «многие ко многим» представлено пустым ромбом и
звездой по обоим краям.
Дейв: Я также вижу кучу пунктирных стрелок на вашей диаграмме.
Тео: Пунктирные стрелки обозначают отношения использования: когда класс использует метод другого класса. Рассмотрим, например, метод Librarian:: blockMember. Он вызывает Member::block.
СОВЕТ. Пунктирные стрелки указывают на отношения использования (рис. 1.4), например когда класс использует метод другого класса.
Рис. 1.3. Отношения ассоциации
Рис. 1.4. Отношения использования:
«многие ко многим»:
класс использует метод другого класса
каждый объект живет независимо
Дейв: Ага. И я предполагаю, что простая стрелка с пустым треугольником, как
между Member и User, представляет наследование.
Тео: Совершенно верно!
СОВЕТ. Простые стрелки с пустыми треугольниками представляют наследование классов (рис. 1.5), где стрелка указывает на суперкласс.
Рис. 1.5. Отношения наследования: класс является производным от другого класса


Глава 1. Сложность объектно-ориентированного программирования
33
1.1.3. Объяснение каждой части диаграммы классов
Дейв: Спасибо за то, что освежил мои знания об UML! Теперь я думаю, что могу
вспомнить, что означают разные стрелки.
Тео: С удовольствием. Хочешь посмотреть, как все это сочетается?
Дейв: На какой класс нам следует обратить внимание в первую очередь?
Тео: Я думаю, нам следует начать с Library.
Класс Library
Library — это корневой класс библиотечной системы. На рис. 1.6 показана структура системы.
Рис. 1.6. Класс Library
С точки зрения кода (поведения) объект Library ничего не делает сам по себе. Он
делегирует все объектам, которыми обладает. С точки зрения данных объект
Library владеет:
несколькими объектами Member;
несколькими объектами Librarian;
одним объектом Catalog.
ПРИМЕЧАНИЕ. В этой книге мы используем термины код и поведение как взаимозаменяемые.
Классы Librarian, Member и User
Оба класса Librarian и Member происходят от User. На рис. 1.7 показано это соотношение.

34
Часть 1. Гибкость
Рис. 1.7. Librarian и Member происходят от User
Класс User представляет собой пользователя библиотеки.
Что касается данных читателя, то они представляют собой абсолютный минимум — у читателя есть идентификатор (id), адрес электронной почты (email) и
пароль (password) (на данный момент без защиты и шифрования).
С точки зрения кода читатель может войти в систему, используя логин.
Класс Member представляет собой читателя.
Он наследуется от User.
Что касается элементов данных, то в нем нет ничего, кроме User.
С точки зрения кода он может:
• проверить книгу через checkout;
• вернуть книгу через returnBook;
• заблокировать себя с помощью block;
• разблокировать себя с помощью unblock;
• ответить, если он заблокирован через isBlocked;
Он владеет несколькими объектами BookLending.
Он использует BookItem для реализации checkout.
Класс Librarian представляет собой библиотекаря.
Он происходит от User.
С точки зрения элементов данных в нем нет ничего кроме User.
С точки зрения кода он может:
• блокировать и разблокировать Member;
• перечислить книги, выданные читателю, через getBookLendings;
• добавлять книги в библиотеку через addBookItem.
Он использует Member для реализации blockMember, unblockMember и getBookLendings.

Глава 1. Сложность объектно-ориентированного программирования
35
Он использует BookItem для реализации проверки.
Он использует BookLending для реализации getBookLendings.
Класс Catalog
Класс Catalog отвечает за управление книгами. На рис. 1.8 показано соотношение
между классами Catalog, Librarian и Book. С точки зрения кода объект Catalog может:
искать книги через search;
добавлять книги в библиотеку через addBookItem.
Рис. 1.8. Класс Catalog
Объект Catalog использует Librarian для реализации addBookItem. С точки зрения
данных каталогу принадлежит несколько объектов Book.
Класс Book
На рис. 1.9 представлен класс Book. С точки зрения данных объект Book: должен иметь как минимум id и title;
связан с несколькими объектами Author (у книги может быть несколько авторов); владеет несколькими объектами BookItem, по одному на каждую копию книги.
Класс BookItem
Класс BookItem представляет копию книги, у книги может быть много копий. С точки зрения данных объект BookItem:
должен иметь как минимум данные для читателей: id и libId (для ID физической
библиотеки);
владеет несколькими объектами BookLending, по одному на каждый раз, когда
книга выдается во временное пользование.
С точки зрения кода объект BookItem может быть извлечен через checkout.

36
Часть 1. Гибкость
Рис. 1.9. Класс Book
1.1.4. Этап реализации
После этого подробного изучения диаграмм Тео Дейв осмысляет их, медленно по-тягивая кофе. Затем он выражает свое восхищение Тео.
Дейв: Ничего себе, это потрясающе!
Тео: Благодарю.
Дейв: Я и не подозревал, что люди действительно тратят время на то, чтобы так
подробно описать свой проект перед написанием кода.
Тео: Я всегда так делаю. Это экономит мне много времени на этапе кодирования.
Дейв: Когда ты начнешь кодить?
Тео: Когда закончу со своим латте.
Тео берет свою кружку с кофе и замечает, что его горячий латте превратился в латте со льдом. Он был так увлечен показом своей классовой диаграммы Дейву, что
забыл его выпить!
1.2. Источники сложности
Пока Тео наливает себе еще одну чашку кофе (на этот раз капучино), я хотел бы
испытать его дизайн. На бумаге он может выглядеть красиво и понятно, но я
утверждаю, что такой дизайн затрудняет понимание системы. Дело не в том, что
Тео выбрал не те классы или что он неправильно понял отношения между классами. Все гораздо серьезнее.
Это о парадигме программирования, которую он выбрал для реализации системы.


Глава 1. Сложность объектно-ориентированного программирования
37
Это об объектно-ориентированной парадигме.
Это о тенденции ООП увеличивать сложность системы.
СОВЕТ. ООП имеет тенденцию создавать сложные системы.
На протяжении всей этой книги я имею в виду тот тип сложности, который затрудняет понимание систем, как определено в статье Бена Мозли и Питера Маркса
«Из дегтярной ямы» (2006), доступной по адресу http://mng.bz/enzq. Это не имеет
ничего общего с типом сложности, который связан с количеством ресурсов, потребляемых программой. Точно так же, когда я говорю о простоте, я имею в виду
несложную (другими словами, легкую для понимания).
Имейте в виду, что сложность и простота (например, трудное и легкое) — это не
абсолютные, а относительные понятия. Мы можем сравнить сложность двух систем
и определить, является ли система A более сложной (или более простой), чем система B.
ПРИМЕЧАНИЕ. Сложность в контексте этой книги означает трудность для понимания.
Как упоминалось во введении к этой главе, в ООП существует множество способов
уменьшить сложность. Цель этой книги — не критиковать ООП, а скорее представить парадигму программирования, называемую дата-ориентированное программирование (ДОП), которая стремится создавать менее сложные системы. На самом
деле парадигма ДОП совместима с ООП.
Если кто-то решит построить систему ООП, которая придерживается принципов
ДОП, система будет менее сложной. Согласно ДОП основными источниками сложности в системе Тео (и многих традиционных системах ООП) являются: смешивание кода и данных;
изменчивость объектов;
блокировка данных в объектах как элементов;
привязка кода к классам как к методам.
Этот анализ аналогичен тому, что функциональное программирование (ФП) думает
о традиционном ООП. Однако, как мы увидим на протяжении всей книги, подход
к обработке данных, который использует ДОП для снижения сложности системы, отличается от подхода ФП. В приложении А мы проиллюстрируем, как применять
принципы ДОП как в ООП, так и в стилях ФП.
СОВЕТ. ДОП совместимо как с ООП, так и с ФП.
В остальных разделах этой главы мы проиллюстрируем каждый из предыдущих
аспектов, обобщенных в табл. 1.1. Мы рассмотрим это в контексте проекта Klafim и объясним, в каком смысле эти аспекты являются источником сложностей.


38
Часть 1. Гибкость
Таблица 1.1. Аспекты ООП и их влияние на сложность системы
Аспект
Влияние на сложность
Код и данные смешаны
Классы, как правило, вовлечены во многие отношения
Объекты изменчивы
При чтении кода требуется повышенная
сосредоточенность
Объекты изменчивы
В многопоточных средах требуется явная синхронизация
Данные заблокированы в объектах
Сериализация данных не является тривиальной задачей
Код заблокирован в классах
Иерархии классов сложны
1.2.1. Множество отношений между классами
Один из способов оценить сложность диаграммы классов — рассматривать только
объекты и их отношения, игнорируя элементы и методы, как показано на рис. 1.10.
Когда мы проектируем систему, мы должны определить отношения между различными частями кода и данными. Это неизбежно.
Рис. 1.10. Обзор диаграммы классов для системы управления библиотекой Klafim СОВЕТ. В ООП код и данные смешиваются в классах: данные как элементы и код как
методы.
С точки зрения системного анализа тот факт, что код и данные смешиваются, делает систему сложной, т. к. объекты, как правило, вовлечены во многие отношения.



Глава 1. Сложность объектно-ориентированного программирования
39
На рис. 1.11 мы более подробно рассмотрим класс Member. Member участвует в пяти
отношениях: двух отношениях данных и трех отношениях кода.
Отношения данных:
• Library имеет много Members;
• Member имеет много BookLendinds.
Отношения кода:
• Member раширяет User;
• Librarian использует Member;
• Member использует BookItem.
Рис. 1.11. Класс Member участвует
Рис. 1.12. Диаграмма классов, на которой Member
в пяти отношениях
разделен на объекты кода и данных
Представьте на мгновение, что нам каким-то образом удалось разделить класс
Member на два отдельных объекта: MemberCode для кода и MemberData для данных. Вместо класса Member с пятью отношениями у нас была бы диаграмма, показанная на
рис. 1.12: объект MemberCode и три отношения; объект MemberData и два отношения.
Диаграмма классов, в которой Member разделен на MemberCode и MemberData, состоит из
двух независимых частей. Каждую часть легче понять, чем исходную диаграмму.
Давайте разделим каждый класс нашей исходной диаграммы классов на код и
объекты данных. На рис. 1.13 показана получившаяся диаграмма. Теперь система
состоит из двух независимых частей:
часть, которая включает только объекты данных;
часть, которая включает только объекты кода.
СОВЕТ. Система, в которой каждый класс разделен на две независимые части — код и
данные, — проще, чем система, в которой код и данные смешаны.
Результирующая система, состоящая из двух независимых подсистем, более понятна, чем исходная система. Тот факт, что две подсистемы независимы, означает, что
каждая подсистема может быть понята отдельно и в любом порядке. Результирую-



40
Часть 1. Гибкость
щая система упростилась не случайно; это логическое следствие отделения кода от
данных.
СОВЕТ. Система, состоящая из нескольких простых независимых частей, менее сложна, чем система, состоящая из одной сложной части.
Рис. 1.13. Диаграмма классов, где каждый класс разделен на код и объекты данных
1.2.2. Непредсказуемое поведение кода
Возможно, вы немного устали после анализа системного уровня, который мы сделали в предыдущем разделе. Давайте отдохнем и посмотрим на код.
Взгляните на код в листинге 1.1, где мы получаем заблокированный статус читателя и отображаем его дважды. Что если я скажу вам, что, когда я вызывал displayBlockedStatusTwice, программа отображала true при первом вызове
console.log?
Листинг 1.1. Действительно простой код
class Member {
isBlocked;
displayBlockedStatusTwice() {
var isBlocked = this.isBlocked;
console.log(isBlocked);
console.log(isBlocked);
}
}
member.displayBlockedStatusTwice();
«Конечно, он снова показал true», — скажете вы. И окажетесь правы!


Глава 1. Сложность объектно-ориентированного программирования
41
Теперь взгляните на немного другой псевдокод, показанный в листинге 1.2. Здесь
мы дважды отображаем заблокированный статус читателя без назначения переменной. Тот же вопрос, что и раньше: если я скажу вам, что, когда я вызывал
displayBlockedStatusTwice, программа отображала true при первом вызове
console.log, можете ли вы сказать мне, что программа отображала при втором вызове console.log?
Листинг 1.2. На первый взгляд простой код
class Member {
isBlocked;
displayBlockedStatusTwice() {
console.log(this.isBlocked);
console.log(this.isBlocked);
}
}
member.displayBlockedStatusTwice();
Правильный ответ: в однопоточной среде отображается true, а в многопоточной
среде это непредсказуемо. Действительно, в многопоточной среде между двумя
вызовами console.log может быть переключение контекста, которое изменяет состояние объекта (например, библиотекарь разблокирует читателя). На самом деле
с небольшой модификацией такая же непредсказуемость кода может возникнуть
даже в однопоточной среде, такой как JavaScript, когда данные изменяются с помощью асинхронного кода (см. раздел о принципе № 3 в приложении A). Разница
между двумя фрагментами кода заключается в том, что:
в первом листинге (листинг 1.1) мы дважды обращаемся к логическому значению, которое является примитивным значением;
во втором листинге (листинг 1.2) мы дважды обращаемся к члену объекта.
СОВЕТ. Когда данные изменяемы, код непредсказуем.
Это непредсказуемое поведение второго листинга является одним из досадных по-следствий ООП. В отличие от примитивных типов, которые обычно неизменяемы, члены объекта изменяемы. Одним из способов решения этой проблемы в ООП
является защита конфиденциального кода с помощью механизмов безопасности
параллелизма, таких как мьютексы, но это приводит к таким проблемам, как снижение производительности и риск взаимоблокировок.
Позже в книге мы увидим, что ДОП обрабатывает каждый фрагмент данных одинаково: как примитивные типы, так и типы коллекций являются неизменяемыми
значениями. Такое ценностное отношение ко всем членам приносит спокойствие
в умы разработчиков ДОП, и появляется больше мозговых клеток для обработки
интересных частей создаваемых ими приложений.
СОВЕТ. Неизменность данных приносит спокойствие разработчикам ДОМ.
42
Часть 1. Гибкость
1.2.3. Нетривиальная сериализация данных
Тео очень устал и засыпает за своим столом. Он видит сон. Во сне Нэнси просит
его сделать систему управления библиотеками Klafim доступной через REST API, используя JSON в качестве транспортного уровня. Тео должен реализовать конечную точку /search, которая получает запрос в формате JSON и возвращает результаты в формате JSON. В листинге 1.3 показан пример ввода конечной точки
/search, а в листинге 1.4 показан пример вывода конечной точки /search.
Листинг 1.3. Ввод JSON конечной точки /search
{
"searchCriteria": "author",
"query": "albert"
}
Листинг 1.4. Вывод JSON конечной точки /search
[
{
"title": "The world as I see it",
"authors": [
{
"fullName": "Albert Einstein"
}
]
},
{
"title": "The Stranger",
"authors": [
{
"fullName": "Albert Camus"
}
]
}
]
Вероятно, Тео реализовал бы конечную точку /search, создав три класса, как показано в следующем листинге и на рис. 1.14. (Неудивительно, что все в ООП должно
быть заключено в класс. Верно?)
SearchController отвечает за обработку запроса.
SearchQuery преобразует строку запроса JSON в данные.
SearchResult преобразует данные результатов поиска в строку JSON.
SearchController (см. рис. 1.14) будет иметь единственный метод handle со следующим потоком:


Глава 1. Сложность объектно-ориентированного программирования
43
Рис. 1.14. Диаграмма классов для SearchController
создает объект SearchQuery из строки запроса JSON;
извлекает searchCriteria и queryStr из объекта SearchQuery;
вызывает метод поиска catalog:Catalog с searchCriteria и queryStr и получает
books:List<Book>;
создает объект SearchResult с Books;
преобразует объект SearchResult в строку JSON.
Как насчет других конечных точек, например тех, которые позволяют библиотекарям добавлять элементы книги через /add-book-item? Тео пришлось бы повторить
точно такой же процесс и создать три класса:
AddBookItemController для обработки запроса;
BookItemQuery для преобразования строки запроса JSON в данные;
BookItemResult для преобразования данных результатов поиска в строку JSON.
Код, связанный с десериализацией JSON, который Тео написал ранее в SearchQuery, придется переписать в BookItemQuery. То же самое для кода, который имеет дело
с сериализацией JSON, который он написал ранее в SearchResult; его нужно было
бы переписать в BookItemResult.
Плохая новость заключается в том, что Тео пришлось бы повторять один и тот же
процесс для каждой конечной точки системы. Каждый раз, когда он сталкивается
с новым видом ввода или вывода JSON, ему придется создавать новый класс и
писать код. Мечта Тео превращается в кошмар!
Внезапно его телефон звонит рядом с тем местом, где он положил голову на стол.
Когда Тео просыпается, он понимает, что Нэнси никогда не просила JSON. Все это
было сном... действительно плохим сном!
СОВЕТ. В ООП сериализация данных затруднена.
Довольно неприятно, что обработка сериализации и десериализации JSON в ООП
требует добавления такого количества классов и написания такого большого коли-

44
Часть 1. Гибкость
чества кода — снова и снова! Разочарование усиливается, если учесть, что сериализация поискового запроса, запроса к товару книги или любого другого запроса
очень похожа. Все сводится к:
просматриванию поля данных;
объединению имени полей данных и значения полей данных, разделенных запятой.
Почему такой простой вещи столь трудно достичь в ООП? В ООП данные должны
соответствовать жесткой форме, определенной в классах, что означает, что данные
заблокированы в элементах. Простого способа общего доступа к данным не существует.
СОВЕТ. В ООП данные блокируются в классах как члены.
Позже мы уточним, что мы подразумеваем под общим доступом к данным, и мы
увидим, как ДОП предоставляет общий способ обработки сериализации и десериализации JSON. До тех пор вам придется продолжать страдать. Но, по крайней мере, вы начинаете осознавать это страдание и знаете, что его можно избежать.
ПРИМЕЧАНИЕ. Большинство языков программирования ООП немного облегчают трудности, связанные с преобразованием из и в JSON. Это либо включает в себя размышления, что, безусловно, сложная вещь, либо многословие кода.
1.2.4. Сложные иерархии классов
Один из способов избежать повторного написания одного и того же кода в ООП
включает наследование классов. Действительно, когда все требования системы известны заранее, вы проектируете свою иерархию классов таким образом, чтобы
классы с общим поведением производились от базового класса.
На рис. 1.15 показан пример этого шаблона, который фокусируется на той части
нашей диаграммы классов, которая касается читателей и библиотекарей. Как
Librarians, так и Members нужна возможность входа в систему, и они наследуют эту
возможность от класса User.
Пока все хорошо, но когда после внедрения системы вводятся новые требования, то это совершенно другая история. Перенесемся в понедельник, 29 марта, 11 утра, когда до дедлайна осталось два дня (среда в полночь).
Нэнси звонит Тео со срочной просьбой. Тео не уверен, сон это или реальность. Он
ущипнул себя и почувствовал щипок. Это определенно реальность!
Нэнси: Как продвигается проект?
Тео: Прекрасно, Нэнси. Мы идем по графику, чтобы уложиться в крайний срок.
Сейчас мы проводим наш последний раунд регрессионных тестов.
Нэнси: Прекрасно! Это означает, что у нас есть время для добавления крошечной
функции в систему, верно?
Тео: Зависит от того, что ты подразумеваешь под «крошечной».


Глава 1. Сложность объектно-ориентированного программирования
45
Нэнси: Нам нужно добавить VIP-читателей в систему.
Тео: Что вы подразумеваете под VIP-читателями?
Нэнси: VIP-читателям разрешается самостоятельно добавлять книги в библиотеку.
Тео: Хм...
Нэнси: Что?
Тео: Это не такое уж и крошечное изменение!
Нэнси: Почему?
Рис. 1.15. Часть диаграммы классов, которая касается читателей (members) и библиотекарей (librarians)
Я задам вам тот же вопрос, который Нэнси задала Тео: почему добавление VIP-читателей в нашу систему не является крошечной задачей? В конце концов, Тео
уже написал код, который позволяет библиотекарям добавлять элементы книги
в библиотеку (это в Librarian::addBookItem). Что мешает ему повторно использовать
этот код для VIP-читателей? Причина в том, что в ООП код привязан к классам как
к методам.
СОВЕТ. В ООП код привязан к классам.
VIP-читатели — это читатели, которым разрешено самостоятельно добавлять книги
в библиотеку. Тео разбивает требования заказчика на две части:
VIP-читатели — это читатели;
VIP-читателям разрешается самостоятельно добавлять книги в библиотеку.
Затем Тео решает, что ему нужен новый класс, VIPMember. Для первого требования
(VIP-читатели являются читателями библиотеки) представляется разумным сделать
VIPMember производным от Member. Однако выполнение второго требования (VIP-


46
Часть 1. Гибкость
читателям разрешается добавлять элементы книги) является более сложным. Он не
может сделать VIPMember производным от Librarian, потому что связь между
VIPMember и Librarian не линейна:
с одной стороны, VIP-читатели подобны библиотекарям в том смысле, что им
разрешено добавлять элементы (книги);
с другой стороны, VIP-читатели не похожи на библиотекарей в том смысле, что
им не разрешается блокировать участников или перечислять книги, предоставленные участнику.
Проблема в том, что код, который добавляет элементы «книги», заблокирован
в классе Librarian. Класс VIPMember не может использовать этот код.
На рис. 1.16 показано одно из возможных решений, которое делает код Librarian:: addBookItem доступным как для классов Librarian, так и для классов VIPMember. Вот
изменения к предыдущей диаграмме классов:
базовый класс UserWithBookItemRight расширяет User;
addBookItem перемещается из библиотеки в UserWithBookItemRight;
и VIPMember, и Librarian расширяют права пользователя с помощью Bookitemright.
Рис. 1.16. Диаграмма классов для системы с VIP-читателями
Это было нелегко, но Тео удается справиться с изменениями вовремя благодаря
тому, что он всю ночь кодит на своем ноутбуке. Он даже смог добавить новые тесты в систему и снова запустить регрессионные тесты. Однако он был так взволнован, что не обратил внимания на ромбовидную проблему, которую VIPMember ввел
в свою диаграмму классов из-за множественного наследования: VIPMember расширяет как Member, так и UserWithBookItemRight, которые оба расширяют User.
В среду, 31 марта, в 10 утра (за 14 часов до дедлайна) Тео звонит Нэнси, чтобы
сообщить ей хорошие новости.


Глава 1. Сложность объектно-ориентированного программирования
47
Тео: Мы смогли вовремя добавить VIP-читателей в систему, Нэнси.
Нэнси: Прекрасно! Я же говорила вам, что это крошечная функция.
Тео: Ну да...
Нэнси: Слушай, я все равно собиралась тебе позвонить. Я только что закончила
встречу со своим деловым партнером, и мы поняли, что нам нужна еще одна
крошечная функция перед запуском. Сможешь ли ты справиться с этим до истечения дедлайна?
Тео: Опять же, это зависит от того, что ты подразумеваешь под «крошечным».
Нэнси: Нам нужно добавить Супер-читателей в систему.
Тео: Что вы подразумеваете под Супер-читателями?
Нэнси: Супер-читателям разрешается указывать книги, предоставленные другим
участникам.
Тео: Э...
Нэнси: Что?
Тео: Это не крошечное изменение!
Нэнси: Почему?
Как и в случае с VIP-читателями, добавление Супер-читателей в систему требует
изменений в иерархии классов Тео. На рис. 1.17 показано решение, которое имеет
в виду Тео.
Рис. 1.17. Диаграмма классов для системы с Супер- и VIP-читателями библиотеки
Добавление Супер-читателей сделало систему действительно сложной. Тео внезапно замечает, что у него на диаграмме классов есть три бриллианта ромбовидной
формы — не драгоценных камней, а трое «смертоносных бриллианта смерти», как
разработчики ООП иногда называют двусмысленность, возникающую, когда класс
D наследуется от двух классов B и C, где оба наследуются от класса A!

48
Часть 1. Гибкость
Он пытается избежать проблем, преобразуя пользовательский класс в интерфейс и
используя шаблон проектирования «композиция поверх наследования». Но из-за
стресса, связанного с приближением дедлайна, он не в состоянии использовать все
клетки своего мозга. На самом деле система стала настолько сложной, что он не
в состоянии закончить систему к установленному сроку. Тео говорит себе, что ему
следовало бы использовать композицию вместо наследования классов. Но теперь
уже слишком поздно.
СОВЕТ. В ООП отдавайте предпочтение композиции, а не наследованию классов.
В 10 вечера, за два часа до дедлайна, Тео звонит Нэнси, чтобы объяснить ситуацию.
Тео: Послушай, Нэнси, мы действительно сделали все, что могли, но мы не сможем
добавить Супер-читателей в систему до истечения дедлайна.
Нэнси: Не беспокойся, мы с моим деловым партнером решили пока опустить эту
функцию. Мы добавим её позже.
Со смешанным чувством гнева и облегчения Тео перестает расхаживать по своему
кабинету. Он понимает, что проведет сегодняшнюю ночь в своей постели, а не будет корпеть над своим компьютером в офисе. Его жена наверняка будет счастлива.
Тео: Я думаю, это означает, что мы готовы к запуску завтра утром.
Нэнси: Да. Мы будем предлагать этот новый продукт в течение месяца или около
того, и если мы получим хорошую поддержку на рынке, мы продвинемся вперед
с более крупным проектом.
Тео: Супер. Тогда давайте свяжемся через месяц. Удачи на старте!
Итоги
Сложность в контексте этой книги означает трудность для понимания.
Мы используем термины код и поведение как взаимозаменяемые.
ДОП расшифровывается как дата-ориентированное программирование.
ООП расшифровывается как объектно-ориентированное программирование.
ФП расшифровывается как функциональное программирование.
В композиционном отношении, когда умирает один объект, умирает и другой.
Композиционное соотношение представлено простым бриллиантом (ромбом) на
одном краю и необязательной звездой на другом краю.
В ассоциативном отношении каждый объект имеет независимый жизненный
цикл.
Ассоциативное отношение «многие ко многим» представлено пустым ромбом
и звездой по обоим краям.
Пунктирные стрелки указывают на отношение использования; например, когда
класс использует метод другого класса.
Глава 1. Сложность объектно-ориентированного программирования
49
Простые стрелки с пустыми треугольниками представляют наследование класса, где стрелка указывает на суперкласс.
Дизайн, представленный в этой главе, не претендует на звание самого умного
дизайна ООП. Опытные разработчики ООП, вероятно, использовали бы пару
шаблонов проектирования и предложили бы гораздо лучшую схему.
Традиционные системы ООП, как правило, увеличивают сложность системы
в том смысле, что системы ООП трудны для понимания.
В традиционном ООП код и данные смешиваются в классах: данные как члены
и код как методы.
В традиционном ООП данные изменяемы.
Основная причина увеличения сложности связана со смешиванием кода и данных вместе в объекты.
Когда код и данные смешаны, классы, как правило, вовлечены во множество
отношений.
Когда объекты изменчивы, требуется дополнительное внимание, чтобы понять, как ведет себя код.
Когда объекты изменяемы, в многопоточных средах требуются явные механизмы синхронизации.
Когда данные заблокированы в объектах, сериализация данных не является тривиальной.
Когда код заблокирован в классах, иерархии классов, как правило, являются
сложными.
Система, в которой каждый класс разделен на две независимые части, код и
данные, проще, чем система, в которой код и данные смешаны.
Система, состоящая из множества простых независимых частей, менее сложна, чем система, состоящая из одной сложной части.
Когда данные изменчивы, код непредсказуем.
Стратегическое использование шаблонов проектирования может помочь в некоторой степени снизить сложность традиционного ООП.
Неизменность данных приносит спокойствие разработчикам ДОП.
Большинство языков программирования ООП немного облегчают трудности, связанные с преобразованием из и в JSON. Это либо включает в себя размышления, что, безусловно, сложная вещь, либо многословие кода.
В традиционном ООП сериализация данных затруднена.
В традиционном ООП данные блокируются в классах как члены.
В традиционном ООП код привязан к классам.
ДОП снижает сложность за счет переосмысления данных.
ДОП совместимо как с ООП, так и с ФП.
50
Часть 1. Гибкость


Разделение кода и данных
Целый новый мир
В ЭТОЙ ГЛАВЕ РАССМАТРИВАЮТСЯ
Преимущества отделения кода от данных
Проектирование системы, в которой код и данные разделены
Внедрение системы, которая соблюдает разделение между кодом
и данными
Первое понимание ДОП заключается в том, что мы можем уменьшить сложность
наших систем, отделив код от данных. Действительно, когда код отделен от данных, наши системы состоят из двух основных частей, о которых можно думать отдельно: объектов данных и модулей кода. Эта глава представляет собой глубокое
погружение в первый принцип ДОП (кратко изложенный на рис. 2.1).
Рис. 2.1. Обобщенный принцип ДОП № 1: отделите код от данных
52
Часть 1. Гибкость
ПРИНЦИП № 1. Отделите код от данных таким образом, чтобы код находился в функциях, поведение которых не зависит от данных, которые каким-то образом инкапсулированы в контексте функции.
В этой главе мы проиллюстрируем разделение между кодом и данными в контексте
системы управления библиотеками Klafim, которую мы представили в главе 1. Мы
также расскажем о преимуществах, которые это разделение приносит системе: система проста. Это легко понять;
система является гибкой и расширяемой. Довольно часто для адаптации к меняющимся требованиям не требуется никаких изменений в дизайне.
Эта глава посвящена разработке кода в системе, где код и данные разделены.
В следующей главе мы сосредоточимся на дизайне данных. По мере продвижения
по книге мы откроем для себя другие преимущества отделения кода от данных.
2.1. Две части системы ДОП
Пока Тео едет домой после отправки прототипа, он спрашивает себя, был ли проект
Klafim успешным или нет. Конечно, он смог удовлетворить клиента, но это была
скорее удача, чем мозги. Он бы не успел вовремя, если бы Нэнси решила сохранить
функцию Супер-читателей. Почему было так сложно добавить в систему крошечные функции? Почему система, которую он построил, была такой сложной? Он
думал, что должен быть способ создавать более гибкие системы!
На следующее утро Тео спрашивает в Hacker News и на Reddit о способах снижения сложности системы и создания гибких систем. Некоторые люди упоминают об
использовании различных языков программирования, в то время как другие говорят о продвинутых шаблонах проектирования. Наконец, внимание Тео привлекает
комментарий от пользователя по имени Джо. Он упоминает дата-ориентированное
программирование, и утверждает, что его главная цель — снизить сложность системы. Тео никогда раньше не слышал этого термина. Из любопытства он решает
связаться с Джо по электронной почте. Какое совпадение! Джо тоже живет в Сан-Франциско. Тео приглашает его на встречу в свой офис.
Джо — 40-летний разработчик. Он был разработчиком Java почти десять лет, прежде чем перейти на Clojure около семи лет назад. Когда Тео рассказывает Джо
о системе управления библиотекой, которую он разработал и построил, и о своих
усилиях по адаптации к меняющимся требованиям, Джо не удивлен.
Джо говорит Тео, что системы, которые он и его команда создали в Clojure за последние семь лет, менее сложные и более гибкие, чем системы, которые он использовал для создания на Java. По словам Джо, системы, которые они создают сейчас, как правило, намного проще, потому что они следуют принципам ДОП.
Тео: Я никогда не слышал о дата-ориентированном программировании. Это новая
концепция?
Джо: И да и нет. Большинство основополагающих идей дата-ориентированного
программирования, или ДОП, как мы любим его называть, хорошо известны


Глава 2. Разделение кода и данных
53
программистам как лучшие практики. Однако новизна ДОП заключается в том, что оно объединяет лучшие практики в единое целое.
Тео: Для меня это немного абстрактно. Можете ли вы привести мне пример?
Джо: Конечно! Возьмем, к примеру, первый принцип ДОП. Речь идет об отношениях между кодом и данными.
Тео: Вы имеете в виду инкапсуляцию данных в объекты?
Джо: На самом деле ДОП выступает против инкапсуляции данных.
Тео: Это почему? Я думал, что инкапсуляция данных — это позитивная парадигма
программирования.
Джо: Инкапсуляция данных имеет как достоинства, так и недостатки. Подумайте
о том, как вы разработали систему управления библиотекой. Согласно ДОП
основной причиной сложности и негибкости систем является то, что код и данные смешаны вместе в объектах.
СОВЕТ. ДОП выступает против инкапсуляции данных.
Тео: Это похоже на то, что я слышал о функциональном программировании. Итак, если я хочу внедрить ДОП, нужно ли мне избавиться от объектно-ориентированного программирования и изучить функциональное программирование?
Джо: Нет, принципы ДОП не зависят от языка. Они могут быть применены как
в объектно-ориентированных, так и в функциональных языках программирования.
Тео: Какое облегчение! Я боялся, что вы собираетесь рассказать мне о монадах, алгебраических типах данных и функциях более высокого порядка.
Джо: Нет, ничего из этого не требуется в ДОП.
СОВЕТ. Принципы ДОП не зависят от языка.
Тео: Как тогда выглядит разделение между кодом и данными в ДОП?
Джо: Данные представлены объектами данных, которые содержат только элементы. Код объединяется в модули, где все функции не имеют состояния.
Тео: Что вы подразумеваете под функциями без состояния?
Джо: Вместо того, чтобы инкапсулировать состояние в объект, объект данных
передается в качестве аргумента.
Тео: Я не понимаю.
Джо: Давайте визуализируем это.
Джо подходит к белой доске и быстро рисует диаграмму, чтобы проиллюстрировать свой комментарий (рис. 2.2).
Тео: Все еще не ясно.

54
Часть 1. Гибкость
Рис. 2.2. Разделение между кодом и данными
Джо: Это станет понятнее, когда я покажу вам, как это выглядит в контексте вашей
системы управления библиотекой.
Тео: Хорошо. Начнем ли мы с кода или с данных?
Джо: Что ж, это дата-ориентированное программирование, так что давайте начнем
с данных.
2.2. Объекты данных
В ДОП мы начинаем процесс проектирования с обнаружения объектов данных нашей системы. Вот что Джо и Тео должны сказать об объектах данных.
Джо: Каковы объекты данных вашей системы?
Тео: Что вы подразумеваете под объектами данных?
Джо: Я имею в виду те части вашей системы, которые хранят информацию.
ПРИМЕЧАНИЕ. Объекты данных — это части вашей системы, которые содержат информацию.
Тео: Это система управления библиотекой, так что у нас есть книги и читатели.
Джо: Конечно, но есть и другие. Один из способов обнаружить объекты данных
системы — это поиск существительных и именных фраз в требованиях системы.
Тео смотрит на бумажную салфетку Нэнси. Он выделяет существительные и слово-сочетания, которые, по-видимому, представляют собой объекты данных.
ВЫДЕЛЕНИЕ ТЕРМИНОВ В ТРЕБОВАНИЯХ, СООТВЕТСТВУЮЩИХ ОБЪЕКТАМ ДАННЫХ
Существуют два типа пользователей: читатели (members) и библиотекари (librarians).
Пользователи (users) входят в систему по электронной почте и паролю.
Читатели могут брать книги взаймы.
Читатели и библиотекари могут искать книги по названию или по автору. Библиотекари могут блокировать и разблокировать пользователей (например, когда они запаздывают с возвратом книги).
Библиотекари могут вносить в список книги, которые в настоящее время предоставлены читателю.
У книги может быть несколько экземпляров.
Джо: Отлично. Можете ли вы увидеть естественный способ сгруппировать эти
объекты?
Тео: Не уверен, но мне кажется, что пользователи, читатели и библиотекари образуют одну группу, в то время как книги, авторы и копии книг образуют другую
группу.

Глава 2. Разделение кода и данных
55
Джо: По-моему, звучит неплохо. Как бы вы назвали каждую группу?
Тео: Вероятно, управление пользователями для первой группы и каталог для второй группы.
ОБЪЕКТЫ ДАННЫХ СИСТЕМЫ, ОРГАНИЗОВАННЫЕ ВО ВЛОЖЕННЫЙ СПИСОК
Данные каталога:
- данные о книгах;
- данные об авторах;
- данные о предметах книги;
- данные о предоставлении книг.
Данные управления пользователями:
- данные о пользователях;
- данные о читателях;
- данные о библиотекарях.
Тео: Я не уверен насчет отношений между книгами и авторами. Должна ли это
быть ассоциация или композиция?
Джо: На данный момент не беспокойтесь слишком сильно о деталях. Позже мы
уточним дизайн наших объектов данных. А пока давайте визуализируем две
группы на ментальной карте.
Тео и Джо немного совещаются. На рис. 2.3 показана составленная ими ментальная
карта.
Рис. 2.3. Объекты данных системы, организованные в виде ментальной карты
Наиболее точный способ визуализации объектов данных системы ДОП — это нарисовать диаграмму объектов данных с различными стрелками для ассоциации и
композиции. Мы вернемся к диаграммам объектов данных позже.

56
Часть 1. Гибкость
СОВЕТ. Откройте для себя объекты данных вашей системы, а затем отсортируйте их по
группам высокого уровня либо в виде вложенного списка, либо в виде ментальной карты.
В следующей главе мы углубимся в проектирование и представление объектов
данных. А пока давайте упростим ситуацию и скажем, что данные нашей библиотечной системы состоят из двух высокоуровневых групп: управление пользователями и каталог.
2.3. Модули кода
Вторым шагом процесса проектирования в ДОП является определение модулей
кода. Давайте еще раз послушаем Джо и Тео.
Джо: Теперь, когда вы определили объекты данных вашей системы и распределили
их по группам высокого уровня, пришло время подумать о кодовой части вашей
системы.
Тео: Что вы подразумеваете под частью кода?
Джо: Один из способов подумать об этом — определить функциональность вашей
системы.
Тео снова смотрит на требования Нэнси. На этот раз он выделяет глагольные фра-зы, которые представляют функциональность.
ВЫДЕЛЕНИЕ ТЕРМИНОВ В ТРЕБОВАНИЯХ, СООТВЕТСТВУЮЩИХ ФУНКЦИОНАЛЬНОСТИ
Существуют два типа пользователей: читатели и библиотекари.
Пользователи входят в систему по электронной почте и паролю.
Читатели могут брать книги взаймы.
Читатели и библиотекари могут искать книги по названию или по автору.
Библиотекари могут блокировать и разблокировать пользователей (например, когда
они запаздывают с возвратом книги).
Библиотекари могут вносить в список книги, которые в настоящее время предоставлены читателю.
У книги может быть несколько экземпляров.
Кроме того, для Тео очевидно, что читатели также могут вернуть книгу. Более того, должен быть способ определить, является ли пользователь библиотекарем или нет.
Он добавляет их к требованиям, а затем перечисляет функциональные возможности
системы.
ФУНКЦИОНАЛЬНОСТЬ БИБЛИОТЕЧНОЙ СИСТЕМЫ
Поиск книги.
Добавление элемента книги.
Блокировка читателя.
Разблокировка читателя.
Вход в систему пользователя.
Внесение в список книг, которые в настоящее время предоставлены читателю.


Глава 2. Разделение кода и данных
57
Взятие книги.
Возвращение книги.
Проверка, является ли пользователь библиотекарем.
Джо: Превосходно! Теперь скажите мне, какая функциональность должна быть от-крыта внешнему миру?
Тео: Что вы подразумеваете под воздействием внешнего мира?
Джо: Представьте, что система управления библиотекой предоставляет API по про-токолу HTTP. Какие функциональные возможности будут доступны конечным
точкам HTTP?
Тео: Что ж, все функциональные возможности системы будут открыты, за исключением проверки того, является ли пользователь библиотекарем.
Джо: Хорошо. Теперь дайте каждой открытой функции краткое имя и соберите их
вместе в поле модуля под названием Library.
Это занимает у Тео меньше минуты. На рис. 2.4 показан модуль, содержащий открытые функции библиотеки, разработанной Тео.
Рис. 2.4. Модуль Library
содержит открытые функции
системы управления
библиотекой
СОВЕТ. Первым шагом в разработке кодовой части системы ДОП является объединение доступных функций в единый модуль.
Джо: Превосходно! Вы только что создали свой первый модуль кода.
Тео: Для меня это выглядит как класс. В чем разница между модулем и классом?
Джо: Модуль — это совокупность функций. В ООП модуль представлен классом, но в других языках программирования это может быть пакет или пространство
имен.
Тео: Ага.
Джо: Важной особенностью модулей кода ДОП является то, что они содержат
только функции без состояния.
Тео: Вы имеете в виду статические методы в Java?
Джо: Да, и классы этих статических методов не должны иметь никаких элементов
данных.
Тео: Итак, как функции узнают, с какой частью информации они работают?
Джо: Легко. Мы передаем это в качестве первого аргумента функции.

58
Часть 1. Гибкость
Тео: Ладно. Можете ли вы привести пример?
Джо, кусая ногти, просматривает список функций модуля Libary на рис. 2.4. Он видит вероятного кандидата.
Джо: Давайте возьмем, к примеру, getBookLendings. В классическом ООП каковы
были бы его аргументы?
Тео: Идентификатор библиотекаря и идентификатор читателя.
Джо: Итак, в традиционном ООП getBookLendings был бы методом класса Library, который получает два аргумента: librarianId и MemberID.
Тео: Так.
Джо: Теперь начинается тонкая часть. В ДОП getBookLendings является частью модуля Library, и он получает LibraryData в качестве аргумента.
Тео: Не могли бы вы показать мне, что вы имеете в виду?
Джо: Конечно!
Джо подходит к клавиатуре Тео и начинает печатать. Он вводит пример того, как
выглядит метод класса в ООП:
class Library {
catalog
userManagement
getBookLendings(userId, memberId) {
// получает доступ к состоянию библиотеки через
this.catalog и this.UserManagement
}
}
Тео: И правда! Метод обращается к состоянию объекта (в нашем случае к библиотечным данным) с помощью этого.
Джо: Могли бы вы сказать, что состояние объекта является аргументом методов
объекта?
Тео: Я бы сказал, что состояние объекта является неявным аргументом для методов
объекта.
СОВЕТ. В традиционном ООП состояние объекта является неявным аргументом для
методов объекта.
Джо: Ну, в ДОП мы передаем данные в качестве явного аргумента. Сигнатура
getBookLendings будет выглядеть следующим образом.
Листинг 2.1. Сигнатура getBookLendings
class Library {
static getBookLendings(libraryData, userId, memberId) {
}
}


Глава 2. Разделение кода и данных
59
Джо: Состояние библиотеки хранится в libraryData, а libraryData передается стати-ческому методу getBookLendings в качестве явного аргумента.
Тео: Это общее правило?
Джо: Абсолютно! То же правило применяется к другим функциям модуля Library, а также к другим модулям. Все модули не имеют состояния — они получают
библиотечные данные, которыми они манипулируют, в качестве аргумента.
СОВЕТ. В ДОП функции модуля кода не имеют состояния. Они получают данные, которыми они манипулируют, в качестве явного аргумента, который обычно является первым
аргументом.
ПРИМЕЧАНИЕ. Модуль — это совокупность функций. В ДОП функции модуля не имеют
состояния.
Тео: Это напоминает мне о Python и о том, как аргумент self появляется в сигнатурах методов. Вот, позвольте мне показать вам пример.
Листинг 2.2. Объект Python как явный аргумент в сигнатурах методов
class Library:
catalog = {}
userManagement = {}
def getBookLendings(self, userId, memberId):
# получает доступ к состоянию библиотеки через self.catalog
и self.userManagement
Джо: Действительно, но разница, о которой я говорю, гораздо глубже, чем изменение синтаксиса. Речь идет о том, что данные находятся вне модулей.
Тео: Я понял это. Как вы сказали, функции модуля не имеют состояния.
Джо: Именно! Хотели бы вы попробовать применить этот принцип ко всему моду-лю Library?
Тео: Конечно.
Тео уточняет дизайн модуля Library, включая подробную информацию об аргумен-тах функций. Он представляет Джо диаграмму на рис. 2.5.
Рис. 2.5. Модуль Library с аргументами функций

60
Часть 1. Гибкость
Джо: Замечательно. Теперь мы готовы приступить к высокоуровневому дизайну
нашей системы.
Тео: Что такое высокоуровневый дизайн в ДОП?
Джо: Высокоуровневый дизайн в ДОП — это определение модулей и взаимодейст-вие между ними.
Тео: Понятно. Существуют ли какие-либо рекомендации, которые помогут мне определить модули?
Джо: Безусловно. Высокоуровневые модули системы соответствуют объектам данных высокого уровня.
Тео: Вы имеете в виду объекты данных, которые отображаются на ментальной карте данных?
Джо: Именно так!
Тео снова смотрит на ментальную карту данных (рис. 2.6). Он фокусируется на
библиотеке объектов данных высокого уровня, каталоге и управлении пользователями. Это означает, что в системе, помимо модуля Library, у нас есть два высокоуровневых модуля:
модуль Catalog работает с данными каталога;
модуль UserManagement имеет дело с данными управления пользователями.
Рис. 2.6. Ментальная карта объектов данных высокого уровня
системы управления библиотекой
Затем Тео рисует высокоуровневый дизайн системы управления библиотекой с модулями Catalog и UserManagement. На рис. 2.7 показано добавление этих модулей, где:
функции Catalog получают в качестве первого аргумента catalogData; функции UserManagement получают userManagementData в качестве первого аргумента.
На данный момент Тео не на 100 % ясно, как объекты данных передаются между
модулями. На данный момент он думает о libraryData как о классе с двумя элементами:
Сatalog содержит данные каталога;
UserManagement содержит данные управления пользователями.
Тео также видит, что функции Library имеют общий шаблон. (Позже в этой главе
мы увидим код некоторых функций модуля Library.)


Глава 2. Разделение кода и данных
61
В качестве аргумента они получают libraryData.
Они передают libraryData.catalog функциям Catalog.
Они передают libraryData.userManagement функциям UserManagement.
СОВЕТ. Модули высокого уровня системы ДОП соответствуют объектам данных высокого уровня.
Рис. 2.7. Модули системы управления библиотекой с аргументами их функций
2.4. Системы ДОП просты для понимания
Тео смотрит на две диаграммы, которые представляют высокоуровневый дизайн
его системы: объекты данных в ментальной карте данных (рис. 2.8) и модули кода
на диаграмме модулей (рис. 2.9).
Немного озадаченный, Тео спрашивает Джо:
Тео: Я не уверен, что эта система лучше, чем традиционная система ООП, где объекты инкапсулируют данные.
Джо: Главное преимущество системы ДОП перед традиционной системой ООП
заключается в том, что ее легче понять.
Тео: Что облегчает ее понимание?
Джо: Дело в том, что система четко разделена на модули кода и объекты данных.
Тео: Как это помогает?
Джо: Когда вы пытаетесь понять объекты данных системы, вам не нужно думать о
деталях кода, который манипулирует объектами данных.
Тео: Значит, когда я смотрю на ментальную карту данных моей системы управления библиотекой, я могу понять это сам?


62
Часть 1. Гибкость
Джо: Точно так же, когда вы пытаетесь понять модули кода системы, вам не нужно
думать о деталях объектов данных, которыми манипулирует код. Существует
четкое разделение проблем между кодом и данными.
Тео снова смотрит на ментальную карту данных на рис. 2.8. У него появляется что-то вроде озарения: «Данные живут сами по себе!».
Рис. 2.8. Ментальная карта данных системы управления библиотекой
ПРИМЕЧАНИЕ. Систему ДОП легче понять, потому что диаграмма разделена на две части: объекты данных и модули кода.
Теперь Тео смотрит на диаграмму модулей на рис. 2.9. Он чувствует себя немного
сбитым с толку и просит Джо разъяснить.
С одной стороны, диаграмма модулей выглядит аналогично диаграммам классов
из классического ООП, поля для классов и стрелки для отношений между классами.
С другой стороны, диаграмма модулей кода выглядит намного проще, чем диаграммы классов из классического ООП, но он не может объяснить почему.
Тео: Диаграмма модулей кажется намного проще, чем диаграммы классов, к которым я привык в ООП. Я чувствую это, но не могу выразить словами.
Джо: Дело в том, что диаграммы модулей имеют ограничения.
Тео: Какого рода ограничения?
Джо: Ограничения на функции, которые мы видели ранее. Все функции статичны
(или не имеют состояния), но существуют также ограничения на отношения
между модулями.
СОВЕТ. Все функции в модуле ДОП не имеют состояния.



Глава 2. Разделение кода и данных
63
Рис. 2.9. Модули системы управления библиотекой с аргументами функции
Тео: Как ограничены отношения между модулями?
Джо: Существует единственный вид связи между модулями ДОП — отношение
использования. Модуль использует код из другого модуля. Нет никакой ассоциации, никакой композиции и никакого наследования между модулями. Это то, что делает схему модуля ДОП простой для понимания.
Тео: Я понимаю, почему нет никакой связи и никакой композиции между модулями ДОП. В конце концов, ассоциация и композиция — это отношения данных.
Но почему нет отношения наследования? Значит ли это то, что ДОП против
полиморфизма?
Джо: Это отличный вопрос! Говоря кратко, что в ДОП мы достигаем полиморфизма с помощью механизма, отличного от наследования классов. Когда-нибудь мы
поговорим об этом.
ПРИМЕЧАНИЕ. Обсуждение полиморфизма в ДОП см. в главе 13.
Тео: Что ж, а вот это уже интересно! Я думал, что наследование — это единственный способ достичь полиморфизма.
Тео снова смотрит на диаграмму модулей на рис. 2.9. Теперь он не только чувствует, что эта диаграмма проще, чем традиционные диаграммы классов ООП, он
понимает, почему она проще: все функции статичны, а все отношения между модулями имеют тип usage. В табл. 2.1 обобщено восприятие Тео.
СОВЕТ. Единственным видом связи между модулями ДОП является отношение использования.
СОВЕТ. Каждую часть системы ДОП легко понять, потому что она предусматривает ограничения.
64
Часть 1. Гибкость
Таблица 2.1. Что облегчает разборку каждой части системы ДОП
Системная часть Ограничение на объекты
Ограничения на отношения
Объекты данных
Только для членов (без кода)
Ассоциация и композиция
Модули кода
Функции без состояния (без членов)
Использование (без наследования)
2.5. Системы ДОП являются гибкими
Тео: Я вижу, как резкое разделение между кодом и данными делает системы ДОП
более понятными, чем классические системы ООП. Но как насчет адаптации
к изменениям в требованиях?
Джо: Еще одним преимуществом систем ДОП является то, что их легко расширять
и адаптировать к меняющимся требованиям.
Тео: Я помню, что, когда Нэнси попросила меня добавить в систему Супер-читателей и VIP-читателей, было трудно адаптировать мою систему ООП. Мне пришлось ввести несколько базовых классов, и иерархия классов стала действительно сложной.
Джо: О, я знаю, что вы имеете в виду. Я столько раз испытывал подобные трудности. Опишите изменения в требованиях к Супер-читателям и VIP-читателям, и я
совершенно уверен, что вы увидите, как легко было бы расширить вашу систему
ДОП.
ТРЕБОВАНИЯ К СУПЕР-ЧИТАТЕЛЯМ И VIP-ЧИТАТЕЛЯМ
Супер-читатели — это читатели, которым разрешено вносить книги в список, предоставленные другим читателям библиотеки.
VIP-читатели — это читатели, которым разрешено добавлять книги в библиотеку.
Тео открывает свою IDE и начинает кодить функцию getBookLendings модуля
Library (см. листинг 2.3), сначала не обращаясь к требованиям для Супер-читателей. Тео помнит, что Джо рассказывал ему о функциях модуля в ДОП: функции не имеют состояния;
функции получают данные, которыми они манипулируют, в качестве своего
первого аргумента.
С точки зрения функциональности getBookLendings состоит из двух частей: проверяет, что пользователь является библиотекарем;
извлекает предоставленные книги из каталога.
По сути, код getBookLendings также состоит из двух частей:
вызывает функцию isLibrarian из модуля UserManagement и передает ей данные
UserManagementData;
вызывает функцию getBookLendings из модуля Catalog и передает ей данные каталога.
Глава 2. Разделение кода и данных
65
Листинг 2.3. Получение одолженных книг от читателя библиотеки
class Library {
static getBookLendings(libraryData, userId, memberId) {
if(UserManagement.isLibrarian(libraryData.userManagement,
userId)) {
return Catalog.getBookLendings(libraryData.catalog,
memberId);
} else {
throw "Not allowed to get book lendings";❶
}
}
}
class UserManagement {
static isLibrarian(userManagementData, userId) {
// будет реализовано позже ❷
}
}
class Catalog {
static getBookLendings(catalogData, memberId) {
// будет реализовано позже ❸
}
}
❶ Существуют и другие способы управления ошибками.
❷ В главе 3 мы увидим, как управлять разрешениями с помощью общих коллекций данных.
❸ В главе 3 мы увидим, как запрашивать данные с помощью общих коллекций данных.
Это первая часть кода ДОП от Тео, и передача всех этих объектов данных —
libraryData, libraryData.UserManagement и libraryData.catalog — кажется немного
неловкой. Но он сделал это! Джо смотрит на код Тео и, кажется, удовлетворен
результатом.
Джо: Теперь как бы вы адаптировали свой код к Супер-читателям библиотеки?
Тео: Я бы добавил функцию isSuperMember в модуль UserManagement и вызвал бы ее
из Library.getBookLendings.
Джо: Совершенно верно! Все очень просто.
Тео набирает код на своем ноутбуке, чтобы показать его Джо. Вот как Тео адапти-рует свой код для Супер-читателей библиотеки.
Листинг 2.4. Позволяющий Супер-читателям получать выданные книги
class Library {
static getBookLendings(libraryData, userId, memberId) {

66
Часть 1. Гибкость
if(Usermanagement.isLibrarian(libraryData.userManagement, userId) ||
Usermanagement.isSuperMember(libraryData.userManagement,
userId)) {
return Catalog.getBookLendings(libraryData.catalog,
memberId);
} else {
throw "Not allowed to get book lendings"; ❶
}
}
}
class UserManagement {
static isLibrarian(userManagementData, userId) {
// будет реализовано позже ❷
}
static isSuperMember(userManagementData, userId) {
// будет реализовано позже ❷
}
}
class Catalog {
static getBookLendings(catalogData, memberId) {
// будет реализовано позже ❸
}
}
❶ Есть и другие способы управления ошибками.
❷ В главе 3 мы увидим, как управлять разрешениями с помощью общих коллекций данных.
❸ В главе 3 мы увидим, как запрашивать данные с помощью общих коллекций данных.
Теперь чувство неловкости, вызванное передачей всех этих объектов данных, пре-обладает над чувством облегчения. Адаптация к этому изменению требований
занимает всего несколько строк кода и не требует изменений в дизайне системы.
И снова Джо, кажется, удовлетворен получившимся результатом.
СОВЕТ. Системы ДОП являются гибкими. Довольно часто они адаптируются к изме-няющимся требованиям, не меняя дизайн системы.
Тео начинает кодить addBookItem. Он смотрит на подпись Library.addBookItem, и значение третьего аргумента bookItemInfo ему непонятно. Он просит Джо разъяснить.
Листинг 2.5. Сигнатура Library.addBookItem
class Library {
static addBookItem(libraryData, userId, bookItemInfo) {
}
}
Глава 2. Разделение кода и данных
67
Тео: Что такое bookItemInfo?
Джо: Давайте назовем это информацией об экземпляре книги. Представьте, что
у нас есть способ представить эту информацию в элементе данных с именем
bookItemInfo.
Тео: Вы имеете в виду объект?
Джо: На данный момент можно думать о bookItemInfo как об объекте. Позже я покажу вам, как мы представляем данные в ДОП.
Помимо этой тонкости в том, как информация об экземпляре книги представлена
bookItemInfo, код для Library.addBookItem в листинге 2.6 очень похож на код, который Тео написал для Library.getBookLendings в листинге 2.4. И снова Тео поражен
тем фактом, что добавление поддержки для VIP-читателей библиотеки не требует
изменения дизайна.
Листинг 2.6. Позволяющий VIP-читателям библиотеки добавлять книги в библиотеку
class Library {
static addBookItem(libraryData, userId, bookItemInfo) {
if(UserManagement.isLibrarian(libraryData.userManagement, userId) ||
UserManagement.isVIPMember(libraryData.userManagement, userId)) {
return Catalog.addBookItem(libraryData.catalog,
bookItemInfo);
} else {
throw "Not allowed to add a book item"; ❶
}
}
}
class UserManagement {
static isLibrarian(userManagementData, userId) {
// будет реализовано позже ❷
}
static isVIPMember(userManagementData, userId) {
// будет реализовано позже ❷
}
}
class Catalog {
static addBookItem(catalogData, memberId) {
// будет реализовано позже ❸
}
}
❶ Существуют и другие способы управления ошибками.
❷ В главе 3 мы увидим, как управлять разрешениями с помощью общих коллекций данных.
❸ В главе 4 мы увидим, как управлять состоянием системы с помощью неизменяемых данных.
68
Часть 1. Гибкость
Тео: Требуется большое изменение мышления, чтобы научиться отделять код от
данных!
Джо: Что было самым сложным для принятия?
Тео: То, что данные не инкапсулируются в объекты.
Джо: То же самое было и со мной, когда я переключился с ООП на ДОП.
Теперь пришло время поесть! — Тео приглашает Джо на обед в Simple, симпатич-ный маленький ресторанчик рядом с офисом.
Итоги
Принципы ДОП не зависят от языка.
Принцип № 1 ДОП заключается в отделении кода от данных.
Разделение между кодом и данными в системах ДОП делает их более простыми
(понятными), чем традиционные системы ООП.
Объекты данных — это части вашей системы, которые содержат информацию.