Используйте объекты-значения
Фуллстек-разработчик Springer Nature описал плюсы объектов-значений на примере Kotlin. Разработчик Noveo Ян перевёл её, заменив язык на РНР.
2К открытий3К показов
Фуллстек-разработчик Springer Nature Луис Соареш (Luís Soares) описал достоинства объектов-значений на примере языка Kotlin. Но объекты-значения могут быть такими же полезными и в других языках! Доказательства — в нашем переводе статьи: разработчик Noveo Ян перевёл её на русский, заменив язык примеров на РНР.
Как убедиться, что два разных компонента «говорят» на одном языке? Как проверить, не разбросаны ли у вас по проекту некорректные данные? Что говорят обычные строки или целые числа о вашей предметной области?
Например, в Java (или PHP, начиная с версии 8.1, — прим. пер.) перечисления являются встроенными в язык объектами-значениями. Тем не менее, они не подходят для произвольного количества значений (и являются скорее технической деталью реализации — прим. пер.). Типичными объектами-значениями являются: адрес электронной почты, пароль, локаль, номер телефона, деньги, IP-адрес, URL, идентификатор сущности, рейтинг, путь до файла, точка, цвет, дата, диапазон дат, расстояние.
Объекты-значения часто упоминаются в связке с предметно-ориентированным проектированием. Давайте рассмотрим их некоторые характеристики на примере языка программирования PHP.
Контейнер данных
Объект-значение — это контейнер произвольных данных:
Непосредственное значение хранится внутри класса.
Обычно объект-значение хранит в себе какое-то одно конкретное значение, но вполне может и больше. Например, цвет в формате RGBA содержит четыре целых числа, а точка в пространстве — три десятичных. Так и между параметрами функции часто может существовать логическая связь. В таком случае их стоит выделить в отдельный класс. Плюсы такого подхода:
- Меньшее количество аргументов функции;
- Явная и однозначная связь между параметрами;
- Ярче выражена идея того, что делает код.
Просто сравните:
В некоторых языках программирования также возможна передача объектов-значений по значению, а не по ссылке. Объекты-значения — спасение от «одержимости примитивами», реальное значение в них — лишь деталь реализации. Например, если вы захотите поменять тип идентификатора сущности User с целого числа на строку, то после этого вам не придётся изменять классы, использующие этот идентификатор, или сигнатуры вроде таких:
Одинаковое значение? Значит равны
Доставая витаминку из упаковки, мы обычно не задумываемся о том, чем одна из них отличается от другой. Так и с объектами-значениями. Пока они несут в себе одинаковое внутреннее значение (или несколько значений), они и сами всегда будут считаться одинаковыми. Даже если ссылки указывают на разное место в оперативной памяти:
Объекты, равные по значению свойств, называются объектами-значениями, — Мартин Фаулер
Неизменяемость
Неизменяемость — главная характеристика объектов-значений и очень важное свойство в функциональном программировании. Такие языки программирования, как Clojure, предлагают объекты-значения в качестве одной из встроенных возможностей языка. В остальных случаях необходимо немного потрудиться, чтобы сделать объекты неизменяемыми — в частности, для предотвращения багов, связанных с алиасингом.
В этом параграфе я решил показать другой пример, чтобы не повторяться с Email, — прим. пер.
Возьмем для примера точку на плоскости. Логично, что однажды созданная точка не может быть изменена, потому что при любом изменении координат это будет уже совершенно другая точка!
Обратите внимание, что в этом примере мы определили только геттеры — методы доступа к координатам точки.
Неизменяемые значения можно свободно передавать из функции в функцию без надобности создавать копии. При этом конкурентный доступ к таким значениям безопасен по умолчанию. Тестирование сводится к созданию отдельных объектов класса через конструктор и проверку возвращаемых значений его методов. Неизменяемые объекты позволяют писать функции без побочных эффектов, а те, в свою очередь, имеют собственные преимущества, — «Объекты-значения», Флориан Бенц
Без идентичности и без истории
Объекты-значения не имеют смысла сами по себе. Они существуют в контексте сущности. Вы могли бы создать объект-значение, например, для запроса вроде findByEmail, но в целом они существуют лишь в качестве данных сущностей.
Сущность обладает идентичностью и жизненным циклом за счет свойств и их изменения со временем. Индивидуальность объекта-значения заключается в его данных. А поскольку эти данные не подвержены изменениям, можно сказать, что собственной «жизни» у объектов-значений нет.
Объектно-ориентированное программирование простым языком — объясняют эксперты
Сущности пребывают, так сказать, в континууме. Они обладают историей изменений в течение времени жизни, даже если мы эту историю нигде не храним. Объекты-значения, напротив, имеют нулевую продолжительность жизни. Мы с легкостью создаем и уничтожаем их. Мы не храним такие значения отдельно. Единственный способ их сохранить — сделать частью сущности, — «Сущность vs Объект-значение», Владимир Хориков
Самопроверка
Как убедиться, например, в том, что все телефонные номера в приложении корректны? С помощью объектов-значений можно предотвратить существование некорректных данных в принципе. Валидация данных на этапе создания объекта способствует инкапсуляции соответствующей логики в одном месте вместо её разброса по всему приложению. Зачастую подобная логика также несёт в себе ценную информацию о предметной области.
Могут содержать логику
Нет ничего страшного в том, чтобы помещать логику в объект-значение, если эта логика непосредственно связана с ним:
В оригинальной статье следующий пример с точками использует перегрузку операторов в Kotlin. Так как в PHP такой возможности нет, мы определим метод add, — прим. пер.:
Несут смысл
Примитивные типы не содержат никакой полезной информации относительно предметной области. С другой стороны, объекты-значения обогащают систему типов, выражают специфику предметной области и оперируют её терминологией, позволяют API и сущностям четко обозначить смысл своих действий, являясь самодокументируемыми кусочками кода.
Давайте рассмотрим несколько примеров. Пусть у нас есть сущность Customer. Большинство её свойств можно представить в виде объектов-значений:
Параметр в первой сигнатуре напоминает мне венгерскую нотацию, плохой стиль программирования на современном языке. Вторая сигнатура фокусируется на смысле параметра — поэтому указание типа в его названии излишне.
Сравните вызовы ниже — второй из них не допускает неясности относительно порядка аргументов:
Объекты-значения ещё более важны в функциональных языках программирования, так как там очень часто используются лямбда-выражения. Также объекты-значения улучшают читабельность кода в языках с поддержкой обобщенного программирования (пример на языке программирования Kotlin, — прим. пер.):
Объекты-значения явно выражают предметную область. Пример — обертка Money вместо двух полей типа BigDecimal и String <…>. Читатели кода поймут, что речь идёт о деньгах, а разработчики не смогут передавать в методы нечто, не представляющее собой деньги. Помимо этого, такая обертка может содержать валидацию, что значит дополнительные сведения о предметной области и повышение безопасности, — «Объект-значения», Флориан Бенц
Бонус: нормализация
Вероятно, однажды вам понадобится снизить строгость относительно пользовательского ввода ради гибкости и во избежание странных багов. Например, вы могли бы автоматически конвертировать ” John.Doe@Gmail.com ” в “john.doe@gmail.com”:
Нормализация — часть парсинга и находится в зоне ответственности I/O. При этом объекты-значения, согласно «Чистой архитектуре», являются частью предметной области. Поэтому нормализация как часть реализации объекта-значения является спорной, необязательной и зависит от конкретной ситуации.
Вы можете исключить «самонормализацию» объектов-значений. Но не забывайте о валидации непосредственно в них. Иначе (как пример) может оказаться, что ваша база данных заполнена одними и теми же адресами электронной почты, отличающимися лишь регистром букв.
Заключение
Объект-значение — это обёртка над неизменяемыми данными, определяемая исключительно своими данными. Сущность, помимо данных, имеет идентификатор. И объекты-значения, и сущности могут содержать бизнес-логику.
«Одержимость примитивами» в коде — это почти так же плохо, как пользовательская форма с текстовыми полями для всего или база данных, сплошь заполненная строками: теряется смысл и допускается ввод некорректных данных. Объекты-значения привносят семантику в API и сущности. В контексте приложения, я рассматриваю объекты-значения на одном уровне с примитивными типами. В некоторых случаях они помогают компилятору (или интерпретатору, — прим. пер.) отловить ошибки на ранней стадии.
Учитывая небольшие затраты и большую пользу, я рекомендую как можно раньше выделять и создавать объекты-значения на проекте. Побуждающим к действию сигналом будет момент, когда вы начнете жонглировать литералами (парсить, разделять, валидировать, конвертировать и так далее).
Объекты-значения имеют ценность и смысл в большинстве языков программирования, объектно-ориентированных и нет. Самое время погуглить на тему «объекты-значения в [мой язык программирования]».
2К открытий3К показов