Введение
Объект, состояние которого нельзя изменить после его создания.
В объектно-ориентированном (ОО) и функциональном программировании неизменяемый объект – это объект, состояние которого нельзя изменить после его создания. Это противопоставляется изменяемому объекту, который можно изменять после создания. В некоторых случаях объект считается неизменяемым, даже если некоторые внутренние атрибуты изменяются, но состояние объекта остаётся неизменным с внешней точки зрения. Например, объект, использующий мемоизацию для кэширования результатов ресурсоёмких вычислений, всё равно может считаться неизменяемым объектом. Строки и другие конкретные объекты обычно реализуются как неизменяемые для повышения читаемости и эффективности выполнения в ОО-программировании. Неизменяемые объекты также полезны, поскольку они по своей природе потокобезопасны. Это, например, может помочь явно обеспечить сохранение определённых инвариантов для данных внутри объекта на протяжении всего его жизненного цикла. В некоторых языках это достигается с помощью ключевого слова (например, `const` в C++, `final` в Java), которое указывает на неизменяемость поля. В других языках всё наоборот: в OCaml поля объекта или записи по умолчанию неизменяемы и должны быть явно помечены как изменяемые (`mutable`).
Ссылки на объекты
В большинстве объектно-ориентированных языков к объектам можно обращаться с помощью ссылок. Примеры таких языков включают Java, C++, C#, VB.NET и многие скриптовые языки, такие как Perl, Python и Ruby. В этом случае важно, может ли состояние объекта изменяться при совместном использовании объектов через ссылки.
Ссылки на объекты против копирования
Если известно, что объект неизменяем, предпочтительнее создавать ссылку на него, а не копировать весь объект. Это делается для экономии памяти за счет предотвращения дублирования данных и избежания вызовов конструкторов и деструкторов; это также может привести к увеличению скорости выполнения. Использовать технику копирования ссылок для изменяемых объектов гораздо сложнее, поскольку любое изменение, внесенное одним пользователем ссылки на изменяемый объект, будет видно всем остальным пользователям этой ссылки. Если это нежелательный эффект, может быть сложно уведомить других пользователей, чтобы они могли правильно отреагировать. В таких случаях защитное копирование всего объекта, а не ссылки, обычно является простым, но затратным решением. Альтернативным способом обработки изменений изменяемых объектов является паттерн "наблюдатель".
Копирование на письменном носителе
Метод, сочетающий преимущества изменяемых и неизменяемых объектов и поддерживаемый непосредственно почти всем современным оборудованием, – это копирование при записи (COW). При использовании этой техники, когда пользователь запрашивает копию объекта, система вместо этого просто создает новую ссылку, указывающую на тот же объект. Как только пользователь пытается изменить объект через конкретную ссылку, система делает фактическую копию, применяет изменение к ней и перенаправляет ссылку на новую копию. Другие пользователи остаются незатронутыми, поскольку они по-прежнему ссылаются на исходный объект. Таким образом, при использовании COW каждый пользователь как будто имеет изменяемую версию своего объекта, однако, если пользователи не изменяют свои объекты, сохраняются преимущества экономии памяти и скорости, присущие неизменяемым объектам. Копирование при записи широко используется в системах виртуальной памяти, поскольку позволяет им экономить память, корректно обрабатывая любые действия, выполняемые прикладной программой.
Интернирование
Практика всегда использования ссылок вместо копий равных объектов называется интернированием. Если применяется интернирование, два объекта считаются равными тогда и только тогда, когда их ссылки, обычно представленные указателями или целыми числами, совпадают. Некоторые языки делают это автоматически: например, Python автоматически интернирует короткие строки. Если алгоритм, реализующий интернирование, гарантированно выполняет его во всех возможных случаях, то проверка объектов на равенство сводится к сравнению их указателей – что значительно повышает скорость работы большинства приложений. (Даже если алгоритм не гарантирует полноту, всё равно существует возможность оптимизации в случае совпадения объектов и использования ими одной и той же ссылки.) Интернирование обычно полезно только для неизменяемых объектов.
Безопасность резьбы
Неизменные объекты могут быть полезны в многопоточных приложениях. Несколько потоков могут работать с данными, представленными неизменными объектами, не опасаясь, что эти данные будут изменены другими потоками. Таким образом, неизменные объекты считаются более потокобезопасными, чем изменяемые объекты.
Нарушение неизменности
Неизменность не подразумевает, что объект, хранящийся в памяти компьютера, нельзя изменить. Скорее, неизменность – это конструкция, проверяемая на этапе компиляции, которая определяет, что программист может делать с объектом через его обычный интерфейс, а не то, что он может сделать в принципе (например, обходя систему типов или нарушая const-корректность в C или C++).
Языковые детали
В Python, Java и .NET Framework строки являются неизменяемыми объектами. И Java, и .NET Framework имеют изменяемые версии строк. В Java это StringBuffer и StringBuilder (изменяемые версии строк Java), а в .NET – StringBuilder (изменяемая версия строки .NET). В Python 3 существует изменяемый вариант строки (байтов) под названием bytearray. Кроме того, все примитивные классы-обертки в Java являются неизменяемыми. Аналогичные подходы – это интерфейс Immutable и Immutable Wrapper. В чисто функциональных языках программирования невозможно создать изменяемые объекты без расширения языка (например, с помощью библиотеки изменяемых ссылок или внешнего интерфейса функций), поэтому все объекты неизменяемы.