Java: Классы
Теория: Состояние объектов
В своей работе мы будем встречаться с двумя типами объектов, одни имеют внутреннее состояние, другие - нет. Ко вторым может, например, относиться объект, задача которого превращать формат markdown в HTML. Подобный объект используется для превращения текста этого урока написанного в формате markdown, в HTML для вывода на сайте. Вот как выглядит использование такого объекта:
В этом примере метод render() никак не влияет на объект md. Метод принимает на вход данные, трансформирует их и возвращает наружу. С таким же успехом, мы могли бы сделать обычный статический метод и использовать его вместо объекта.
Зачем в таком случае нам нужен объект? Есть несколько причин, одна из которых - конфигурация. Преобразование markdown в HTML делается по определенным правилам, которые можно менять, например, делать из урлов html-ссылки или нет.
Таким образом мы можем создать несколько разных видов объектов с разной конфигурацией и затем использовать их в приложении одновременно. В каком-то смысле это тоже состояние, но это не начальное состояние объекта, которое в процессе меняется с помощью методов. Это, как правило, неизменяемое (иммутабельное) состояние, которое используется методами в процессе работы. Сами методы на объект не влияют, поэтому сюда относятся и data-классы с неизменяемым содержимым. Подобные объекты очень просты в создании и работе. С ними редко возникают какие-то сложности.
Совсем другое дело, когда речь идет про объекты с состоянием. Классический пример это пользователь. Внутри такого объекта хранятся данные, которые могут меняться в процессе жизни этого объекта.
Точно таким же объектом с состоянием является и массив
Чем дальше в программировании, тем больше вы будете замечать, как управление состоянием объектов становится одной из самых сложных частей приложения. Разберем несколько примеров.
Многопоточность
Эта тема изучается в самом конце, но о ней нельзя не сказать говоря о состоянии объектов. Изменение одного объекта из разных потоков (поток это единица выполнения, в одной запущенной программе может быть много потоков) может приводить к непредсказуемому результату, когда данные не соответствуют тому что ожидается. Для решения этой проблемы используются специальные механизмы синхронизации.
Конструирование и изменение
Постоянно возникает вопрос, как правильно создавать объект, передавая все данные в конструктор или заполняя их через сеттеры? Во втором случае мы можем получить объект, который прямо сейчас находится в состоянии, когда его нельзя использовать, так как он не готов. Представьте что у нас есть требование создавать пользователя с обязательным заполнением имени и email. Вот что мы можем получить.
В ситуациях когда это возможно, лучше предпочитать способ с заполнением через конструктор, тогда объекты будут готовы сразу после создания.
Целостность состояния (Инварианты)
В случае объектов, инвариантами называются правила, которым должно соответствовать внутреннее состояние объекта. Если эти правила нарушаются, значит в программе есть баги. Например, у объекта описывающего банковский счет, при снятии денег, должна быть проверка на достаточность денег на счету.
Одна из ключевых задач программиста при проектировании классов, учитывать инварианты и реализовывать их в коде.
Зависимые объекты
На практике объекты часто хранят внутри себя ссылки на другие объекты. Что может легко приводить к нарушению инвариантов без возможности это контролировать. Представьте что у нас в коде есть сотрудник и есть компания, которую можно получить так employee.getCompany().
Так как компания это отдельный объект, то ничто не мешает менять его напрямую без взаимодействия с employee. Причем сразу двумя способами.
Если состояние объекта employee зависит от состояния company, то в момент таких изменений может произойти нарушение инвариантов, так как объект employee ничего не знает об изменении.


