Неизменяемый объект

редактировать
Объект, состояние которого не может быть изменено после его создания

В объектно-ориентированном и функциональное программирование, неизменяемый объект (неизменяемый объект) - это объект, состояние которого не может быть изменено после его создания. В этом отличие от изменяемого объекта (изменяемый объект), который можно изменить после его создания. В некоторых случаях объект считается неизменным, даже если некоторые внутренние атрибуты меняются, но состояние объекта кажется неизменным с внешней точки зрения. Например, объект, который использует мемоизацию для кэширования результатов дорогостоящих вычислений, может по-прежнему считаться неизменяемым объектом.

Строки и другие конкретные объекты обычно выражаются как неизменяемые объекты, чтобы улучшить читаемость и эффективность выполнения в объектно-ориентированном программировании. Неизменяемые объекты также полезны, потому что они по своей сути потокобезопасны. Другие преимущества заключаются в том, что они проще для понимания и обоснования, а также обеспечивают более высокий уровень безопасности, чем изменяемые объекты.

Содержание
  • 1 Основные понятия
    • 1.1 Неизменяемые переменные
    • 1.2 Слабая и сильная неизменяемость
    • 1.3 Ссылки на объекты
    • 1.4 Ссылки и копирование объектов
    • 1.5 Копирование при записи
    • 1.6 Интернирование
    • 1.7 Безопасность потоков
    • 1.8 Нарушение неизменяемости
  • 2 Специфичные для языка детали
    • 2.1 Ada
    • 2.2 C #
    • 2.3 C ++
    • 2.4 D
    • 2.5 Java
    • 2.6 Perl
    • 2.7 Python
    • 2.8 JavaScript
    • 2.9 Racket
    • 2.10 Rust
    • 2.11 Scala
  • 3 См. Также
  • 4 Ссылки
  • 5 Внешние ссылки
Концепции

Неизменяемые переменные

В императивном программировании значения, хранящиеся в переменных программы, содержимое которого никогда не изменяется, известны как константы, чтобы отличать их от переменных, которые могут быть изменены во время выполнения. Примеры включают коэффициенты преобразования из метров в футы или значение пи с точностью до нескольких десятичных знаков.

Поля только для чтения могут вычисляться при запуске программы (в отличие от констант, которые известны заранее), но никогда не изменяются после их инициализации.

Слабая и сильная неизменяемость

Иногда говорят о неизменности определенных полей объекта. Это означает, что нет возможности изменить эти части состояния объекта, даже если другие части объекта могут быть изменяемыми (слабо неизменяемыми). Если все поля неизменяемы, объект неизменен. Если весь объект не может быть расширен другим классом, объект называется строго неизменяемым. Это может, например, помочь явным образом обеспечить соблюдение определенных инвариантов относительно того, что определенные данные в объекте остаются неизменными в течение всего времени существования объекта. В некоторых языках это делается с помощью ключевого слова (например, constв C ++, finalв Java ), которое обозначает поле как неизменяемое.. В некоторых языках это наоборот: в OCaml поля объекта или записи по умолчанию неизменяемы и должны быть явно помечены как mutable, чтобы это было так.

Ссылки на объекты

В большинстве объектно-ориентированных языков на объекты можно ссылаться с помощью ссылок. Некоторыми примерами таких языков являются Java, C ++, C#, VB.NET и многие языки сценариев, например Perl, Python и Ruby. В этом случае имеет значение, может ли состояние объекта меняться, когда объекты совместно используются через ссылки.

Ссылки и копирование объектов

Если известно, что объект является неизменяемым, рекомендуется создать ссылку вместо копирования всего объекта. Это сделано для экономии памяти, предотвращая дублирование данных и избегая вызовов конструкторов и деструкторов; это также приводит к потенциальному увеличению скорости выполнения.

Технику копирования ссылок намного сложнее использовать для изменяемых объектов, потому что, если какой-либо пользователь ссылки на изменяемый объект изменяет ее, все другие пользователи этой ссылки видят это изменение. Если это не ожидаемый эффект, может быть сложно уведомить других пользователей, чтобы они ответили правильно. В таких ситуациях защитное копирование всего объекта, а не ссылки, обычно является простым, но дорогостоящим решением. Шаблон наблюдателя - это альтернативный метод обработки изменений изменяемых объектов.

Копирование при записи

Метод, сочетающий в себе преимущества изменяемых и неизменяемых объектов и поддерживаемый напрямую практически во всем современном оборудовании, это копирование при записи (COW). Используя этот метод, когда пользователь просит систему скопировать объект, вместо этого он просто создает новую ссылку, которая по-прежнему указывает на тот же объект. Как только пользователь пытается изменить объект с помощью определенной ссылки, система создает реальную копию, применяет к ней изменения и устанавливает ссылку для ссылки на новую копию. Остальные пользователи не затронуты, потому что они по-прежнему ссылаются на исходный объект. Следовательно, в COW все пользователи имеют изменяемую версию своих объектов, хотя в случае, если пользователи не изменяют свои объекты, сохраняются преимущества неизменяемых объектов в экономии места и скорости. Копирование при записи популярно в системах с виртуальной памятью, потому что оно позволяет им экономить пространство памяти, при этом правильно обрабатывая все, что может делать прикладная программа.

Интернирование

Практика всегда использования ссылок вместо копий одинаковых объектов известна как интернирование. Если используется интернирование, два объекта считаются равными тогда и только тогда, когда их ссылки, обычно представленные как указатели или целые числа, равны. Некоторые языки делают это автоматически: например, Python автоматически стажирует короткие строки. Если алгоритм, реализующий интернирование, гарантированно будет делать это во всех возможных случаях, то сравнение объектов на равенство сводится к сравнению их указателей - существенный выигрыш в скорости в большинстве приложений. (Даже если не гарантируется, что алгоритм будет всеобъемлющим, все же существует возможность улучшения случая fast path, когда объекты равны и используют одну и ту же ссылку.) Интернирование обычно полезно только для неизменяемых объектов.

Безопасность потоков

Неизменяемые объекты могут быть полезны в многопоточных приложениях. Несколько потоков могут действовать с данными, представленными неизменяемыми объектами, не беспокоясь о том, что данные изменяются другими потоками. Поэтому неизменяемые объекты считаются более поточно-ориентированными, чем изменяемые объекты.

Нарушение неизменяемости

Неизменяемость не означает, что объект, хранящийся в памяти компьютера, не может быть записан. Скорее, неизменяемость - это конструкция времени компиляции, которая указывает, что программист может делать через обычный интерфейс объекта, не обязательно то, что он может делать абсолютно (например, обходя систему типов или нарушая const корректность в C или C ++ ).

Особенности языка

В Python, Java и .NET Framework строки являются неизменяемыми объектами. И Java, и.NET Framework имеют изменяемые версии строки. В Java это StringBuffer и StringBuilder (изменяемые версии Java String ) а в.NET это StringBuilder (изменяемая версия.Net String ). Python 3 имеет вариант изменяемой строки (байтов), названный bytearray.

Кроме того, все классы-оболочки примитивов в Java неизменяемы.

Подобные шаблоны - Неизменяемый интерфейс и.

В чистых языках функционального программирования невозможно создавать изменяемые объекты без расширения языка (например, через библиотеку изменяемых ссылок или интерфейс внешней функции ), поэтому все объекты неизменны.

Ada

В Ada любой объект объявляется либо переменной (т. Е. Изменяемой; обычно неявное значение по умолчанию), либо константой(т. Е. Неизменной) через ключевое слово константы.

тип Some_type - новое целое число; - может быть что угодно более сложное x: constant Some_type: = 1; - неизменяемый y: Some_type; - mutable

Параметры подпрограммы неизменны в режиме входа и изменяются в режимах входа и выхода.

процедура Do_it (a: in Integer; b: in out Integer; c: out Integer) is begin - a неизменна b: = b + a; с: = а; конец Do_it;

C #

В C # вы можете обеспечить неизменность полей класса с помощью оператора readonly. Установив все поля как неизменяемые, вы получите неизменяемый тип.

класс AnImmutableType {общедоступное двойное значение только для чтения; общедоступный AnImmutableType (двойной x) {_value = x; } public AnImmutableType Square () {вернуть новый AnImmutableType (_value * _value); }}

C ++

В C ++ const-corrective реализация Cartпозволит пользователю объявлять новые экземпляры класса как const(неизменяемый) или изменяемый, по желанию, путем предоставления двух разных версий метода getItems (). (Обратите внимание, что в C ++ нет необходимости - и фактически невозможно - предоставлять специализированный конструктор для экземпляров const.)

class Cart {public: Cart (std :: vector items) : items_ (std :: move (items)) {} std :: vector items () {return items_; } const std :: vector items () const {return items_; } int ComputeTotalCost () const {/ * возвращает сумму цен * /} private: std :: vector items_; };

Обратите внимание: если есть поле, которое является указателем или ссылкой на другой объект, тогда все еще может быть возможно изменить объект, на который указывает такой указатель или ссылка в константном методе, без нарушения константной корректности. Можно утверждать, что в таком случае объект на самом деле не является неизменным.

C ++ также обеспечивает абстрактную (в отличие от побитовой) неизменяемость с помощью ключевого слова mutable, которое позволяет изменять переменную-член из метода const.

class Cart {public: Cart (std :: vector items): items_ (std :: move (items)) {} const std :: vector items () const {return items_; } int ComputeTotalCost () const {если (total_cost_) {return * total_cost_; } int total_cost = 0; для (const auto item: items_) {total_cost + = item.Cost (); } total_cost_ = total_cost; вернуть total_cost; } частные: std :: vector items_; изменяемый std :: optional total_cost_; };

D

В D существуют два квалификатора типа , constи неизменяемыйдля переменных, которые нельзя изменить. В отличие от C ++ const, Java finalи C # readonly, они транзитивны и рекурсивно применяются ко всему, что доступно через ссылки на такую ​​переменную. Разница между constи неизменяемымзаключается в том, к чему они применяются: const- это свойство переменной: юридически могут существовать изменяемые ссылки на указанное значение, т. Е. значение действительно может измениться. Напротив, неизменяемыйявляется свойством указанного значения: значение и все, что транзитивно достижимо из него, не может измениться (без нарушения системы типов, что приводит к неопределенному поведению ). Любая ссылка на это значение должна быть помечена constили immutable. В основном для любого неквалифицированного типа T, const (T)является несвязным объединением T(изменяемый) и неизменяемый (T).

класс C {/ * изменяемый * / Объект mField; const Object cField; неизменяемый объект iField; }

Для изменяемого объекта Cего mFieldможет быть записан. Для объекта const (C), mFieldне может быть изменен, он наследует const; iFieldостается неизменным, поскольку это более надежная гарантия. Для неизменяемого (C)все поля неизменяемы.

В такой функции:

void func (C m, const C c, неизменяемый C i) {/ * внутри фигурных скобок * /}

Внутри фигурных скобок, cможет относиться к тому же объекту, что и m, поэтому мутации в mтакже могут косвенно изменить c. Кроме того, cможет относиться к тому же объекту, что и i, но поскольку значение в этом случае является неизменным, изменений не происходит. Однако mи iне могут юридически ссылаться на один и тот же объект.

На языке гарантий mutable не имеет никаких гарантий (функция может изменить объект), const- только внешняя гарантия того, что функция ничего не изменит, и immutable- это двунаправленная гарантия (функция не изменит значение, и вызывающий не должен его изменять).

Значения, которые являются constили неизменяемым, должны быть инициализированы прямым присваиванием в точке объявления или конструктором .

Поскольку параметры constзабывают, было ли значение изменяемым или нет, аналогичная конструкция, inout, в некотором смысле действует как переменная для информации об изменчивости. Функция типа const (S) function (const (T))возвращает const (S)типизированные значения для изменяемых, константных и неизменяемых аргументов. Напротив, функция типа inout (S) function (inout (T))возвращает Sдля изменяемых аргументов T, const (S)для значений const (T)и неизменяемый (S)для неизменяемых (T)значений.

Преобразование неизменяемых значений в изменяемые приводит к неопределенному поведению при изменении, даже если исходное значение происходит из изменяемого источника. Преобразование изменяемых значений в неизменяемые может быть законным, если после этого не останется изменяемых ссылок. «Выражение может быть преобразовано из изменяемого (...) в неизменяемое, если выражение уникально и все выражения, на которые оно транзитивно ссылается, являются уникальными или неизменяемыми». Если компилятор не может доказать уникальность, приведение может быть выполнено явно, и программист должен убедиться, что не существует изменяемых ссылок.

Тип stringявляется псевдонимом для immutable (char), то есть типизированного фрагмента памяти неизменяемых символов. Создание подстрок дешево, поскольку оно просто копирует и изменяет указатель и поле длины, и безопасно, поскольку базовые данные не могут быть изменены. Объекты типа const (char)могут ссылаться на строки, но также и на изменяемые буферы.

Создание неглубокой копии константного или неизменяемого значения удаляет внешний слой неизменяемости: копирование неизменяемой строки (неизменяемое (char)) возвращает строку (неизменяемое (char)). Неизменяемый указатель и длина копируются, и копии изменяемы. Указанные данные не были скопированы и сохраняют свой квалификатор, в примере неизменяемый. Его можно удалить, сделав копию, например используя функцию dup.

Java

Классическим примером неизменяемого объекта является экземпляр класса Java String

String s = "ABC"; s.toLowerCase ();

Метод toLowerCase ()не изменяет данные «ABC», которые содержит s. Вместо этого создается новый объект String, которому во время его создания предоставляются данные «abc». Ссылка на этот объект String возвращается методом toLowerCase (). Чтобы строка sсодержала данные «abc», необходим другой подход:

s = s.toLowerCase ();

Теперь String sссылается на новый объект String, содержащий «abc». В синтаксисе объявления класса String нет ничего, что считало бы его неизменным; скорее, ни один из методов класса String никогда не влияет на данные, содержащиеся в объекте String, что делает его неизменным.

Ключевое слово final(подробная статья ) используется для реализации неизменяемых примитивных типов и ссылок на объекты, но само по себе не может сделать сами объекты неизменяемыми. См. Примеры ниже:

Переменные примитивного типа (int, long, shortи т. Д.) Могут быть переназначены после определения. Этого можно избежать, используя final.

int i = 42; // int - это примитивный тип i = 43; // ОК final int j = 42; j = 43; // не компилируется. j является final, поэтому его нельзя переназначить.

Ссылочные типы нельзя сделать неизменяемыми, просто используя ключевое слово final. finalтолько предотвращает переназначение.

final MyObject m = новый MyObject (); // m имеет ссылочный тип m.data = 100; // ОК. Мы можем изменить состояние объекта m (m является изменяемым, и final не меняет этого факта) m = new MyObject (); // не компилируется. m является окончательным, поэтому его нельзя переназначить

Примитивные оболочки (Integer, Long, Short, Double, Float, Character, Byte, Boolean) также являются неизменяемыми. Неизменяемые классы могут быть реализованы, следуя нескольким простым рекомендациям.

Perl

В Perl можно создать неизменяемый класс с библиотекой Moo, просто объявив все атрибуты только чтение:

пакет Immutable; используйте Moo; has value =>(is =>'ro', # только для чтения по умолчанию =>'data', # можно переопределить, указав в конструкторе # значение: Immutable->new (value =>'something else');) ; 1;

Для создания неизменяемого класса требовалось два шага: во-первых, создание средств доступа (автоматически или вручную), предотвращающих изменение атрибутов объекта, и, во-вторых, предотвращение прямого изменения данных экземпляра экземпляров этого класса (обычно это сохранялось в ссылке на хэш и может быть заблокирован функцией lock_hash Hash :: Util):

package Immutable; используйте строгий; использовать предупреждения; использовать базовый qw (Class :: Accessor); # создать средства доступа только для чтения __PACKAGE __->mk_ro_accessors (qw (value)); используйте Hash :: Util 'lock_hash'; sub новый {мой $ class = shift; вернуть $ class, если ref ($ class); die "Аргументы для нового должны быть пар ключ =>значение \ n", если (@_% 2 == 0); мои% defaults = (значение =>'данные',); мой $ obj = {% по умолчанию, @_,}; благослови $ obj, $ class; # запретить модификацию данных объекта lock_hash% $ obj; } 1;

Или с помощью написанного вручную средства доступа:

package Immutable; используйте строгий; использовать предупреждения; используйте Hash :: Util 'lock_hash'; sub новый {мой $ class = shift; вернуть $ class, если ref ($ class); die "Аргументы для нового должны быть пар ключ =>значение \ n", если (@_% 2 == 0); мои% defaults = (значение =>'данные',); мой $ obj = {% по умолчанию, @_,}; благослови $ obj, $ class; # запретить модификацию данных объекта lock_hash% $ obj; } # вспомогательное значение средства доступа только для чтения {my $ self = shift; if (my $ new_value = shift) {# пытаюсь установить новое значение die "Этот объект нельзя изменить \ n"; } else {return $ self ->{значение}}} 1;

Python

В Python некоторые встроенные типы (числа, логические значения, строки, кортежи, замороженные наборы) неизменяемы, но пользовательские классы обычно изменяемы. Чтобы имитировать неизменяемость в классе, можно переопределить установку и удаление атрибута, чтобы вызвать исключения:

class ImmutablePoint: "" "Неизменяемый класс с двумя атрибутами 'x' и 'y'." "" __Slots__ = ['x', 'y'] def __setattr __ (self, * args): raise TypeError ("Невозможно изменить неизменяемый экземпляр.") __delattr__ = __setattr__ def __init __ (self, x, y): # Мы больше не можем использовать self.value = value для хранения данных экземпляра # поэтому мы должны явно вызвать суперкласс super ().__ setattr __ ('x', x) super ().__ setattr __ ('y', y)

Стандартные помощники библиотеки collections. namedtuple и typing.NamedTuple, доступные начиная с Python 3.6, создают простые неизменяемые классы. Следующий пример примерно эквивалентен приведенному выше, плюс некоторые функции, похожие на кортежи:

от ввода import NamedTuple import collections Point = collections. namedtuple ('Point', ['x', 'y']) # следующее создает подобный namedtuple классу Point (NamedTuple): x: int y: int

Представленный в Python 3.7, классы данных позволяют разработчикам эмулировать неизменяемость с помощью замороженных экземпляров. Если построен замороженный класс данных, классы данныхпереопределят __setattr __ ()и __delattr __ (), чтобы вызвать FrozenInstanceErrorпри вызове.

из классов данных import dataclass @dataclass (frozen = True) class Point: x: int y: int

JavaScript

В JavaScript все примитивные типы (Undefined, Null, Boolean, Number, BigInt, String, Symbol) неизменяемы, но пользовательские объекты обычно изменяемы.

function doSomething (x) {/ * изменение x здесь меняет оригинал? * /}; var str = 'строка'; var obj = {an: 'объект'}; doSomething (str); // строки, числа и типы bool неизменяемы, функция получает копию doSomething (obj); // объекты передаются по ссылке и могут изменяться внутри функции doAnotherThing (str, obj); // `str` не изменился, но ʻobj` может измениться.

Чтобы имитировать неизменяемость объекта, можно определить свойства как доступные только для чтения (возможность записи: false).

var obj = {}; Object.defineProperty (obj, 'foo', {значение: 'bar', доступно для записи: false}); obj.foo = 'bar2'; // молча игнорируется

Однако описанный выше подход по-прежнему позволяет добавлять новые свойства. В качестве альтернативы можно использовать Object.freeze, чтобы сделать существующие объекты неизменяемыми.

var obj = {foo: 'bar'}; Object.freeze (obj); obj.foo = 'бары'; // невозможно редактировать свойство, игнорируется автоматически obj.foo2 = 'bar2'; // невозможно добавить свойство, игнорируется без уведомления

С реализацией ECMA262 JavaScript имеет возможность создавать неизменяемые ссылки, которые нельзя переназначить. Однако использование объявления constне означает, что значение ссылки только для чтения является неизменяемым, просто имя не может быть присвоено новому значению.

const ALWAYS_IMMUTABLE = true; попробуйте {ALWAYS_IMMUTABLE = false; } catch (err) {console.log («Невозможно переназначить неизменяемую ссылку.»); } const arr = [1, 2, 3]; arr.push (4); console.log (об); // [1, 2, 3, 4]

Использование неизменяемого состояния стало растущей тенденцией в JavaScript с момента появления React, который отдает предпочтение шаблонам управления состоянием, подобным Flux, таким как Redux.

Racket

Racket существенно отличается от других реализаций схемы Scheme, делая его основной тип пары («cons-ячейки») неизменяемым. Вместо этого он предоставляет параллельный изменяемый тип пары, через mcons, mcar, set-mcar!и т. Д. Кроме того, поддерживаются многие неизменяемые типы, например неизменяемые строки и векторы, и они широко используются. Новые структуры по умолчанию неизменяемы, если только поле специально не объявлено изменяемым, или вся структура:

(struct foo1 (x y)); все поля неизменяемы (struct foo2 (x [y #: mutable])); одно изменяемое поле (struct foo3 (x y) #: mutable); все поля изменяемые

Язык также поддерживает неизменяемые хеш-таблицы, реализованные функционально, и неизменяемые словари.

Rust

Система владения в Rust позволяет разработчикам объявлять неизменяемые переменные и передавать неизменяемые ссылки. По умолчанию все переменные и ссылки неизменяемы. Изменяемые переменные и ссылки явно создаются с помощью ключевого слова mut.

Постоянные элементы в Rust всегда неизменяемы.

// постоянные элементы всегда неизменяемы const ALWAYS_IMMUTABLE: bool = true; struct Object {x: usize, y: usize,} fn main () {// явно объявить изменяемую переменную let mut mutable_obj = Object {x: 1, y: 2}; mutable_obj.x = 3; // хорошо let mutable_ref = mut mutable_obj; mutable_ref.x = 1; // хорошо let immutable_ref = mutable_obj; immutable_ref.x = 3; // ошибка E0594 // по умолчанию переменные неизменяемы let immutable_obj = Object {x: 4, y: 5}; immutable_obj.x = 6; // ошибка E0596 let mutable_ref2 = mut immutable_obj; // ошибка E0596 let immutable_ref2 = immutable_obj; immutable_ref2.x = 6; // ошибка E0594}

Scala

В Scala любая сущность (узко, привязка) может быть определена как изменяемая или неизменяемая: в объявлении можно использовать val(значение) для неизменяемых сущностей и var(переменная) для изменяемых. Обратите внимание, что даже несмотря на то, что неизменяемая привязка не может быть переназначена, она все еще может относиться к изменяемому объекту, и по-прежнему можно вызывать изменяющие методы для этого объекта: привязка неизменна, но базовый объект может быть изменяемым.

Например, следующий фрагмент кода:

val maxValue = 100 var currentValue = 1

определяет неизменяемую сущность maxValue(целочисленный тип определяется во время компиляции) и изменяемая сущность с именем currentValue.

По умолчанию классы коллекций, такие как Listи Map, являются неизменяемыми, поэтому методы обновления возвращают новый экземпляр, а не изменяют существующий один. Хотя это может показаться неэффективным, реализация этих классов и их гарантии неизменности означают, что новый экземпляр может повторно использовать существующие узлы, что, особенно в случае создания копий, очень эффективно.

См. Также
Ссылки

Эта статья содержит некоторые материалы из Perl Книга шаблонов проектирования

Внешние ссылки
Найдите mutable в Wiktionary, бесплатном словаре.
Последняя правка сделана 2021-05-23 12:15:08
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте