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

Я пытаюсь поднять голову вокруг изменчивых и неизменяемых объектов. Использование измененных объектов получает много плохого нажатия (например, возвращает массив строк из метода), но мне трудно понять, каковы негативные последствия этого. Каковы наилучшие методы использования изменяемых объектов? Если вы избегаете их, когда это возможно?

Ответ 1

Ну, есть пара аспектов этого. Номер один, изменяемые объекты без ссылочной идентификации могут вызывать ошибки в нечетные времена. Например, рассмотрим Person bean со значением equals:

Map<Person, String> map = ...
Person p = new Person();
map.put(p, "Hey, there!");

p.setName("Daniel");
map.get(p);       // => null

Экземпляр Person получает "потерянный" на карте при использовании в качестве ключа, поскольку он hashCode и равенство основаны на изменяемых значениях. Эти значения изменились за пределами карты, и все хеширование стало устаревшим. Теоретики любят спорить по этому поводу, но на практике я не считаю, что это слишком большая проблема.

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

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

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

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

Ответ 2

Неизменяемые предметы против неизменных коллекций

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

Неизменная коллекция - это коллекция, которая никогда не меняется.

Когда я выполняю операцию с изменяемой коллекцией, я изменяю коллекцию на месте, и все сущности, которые имеют ссылки на коллекцию, увидят это изменение.

Когда я выполняю операцию с неизменяемой коллекцией, в новую коллекцию возвращается ссылка, отражающая это изменение. Все объекты, которые имеют ссылки на предыдущие версии коллекции, не увидят изменения.

Умные реализации не обязательно должны копировать (клонировать) всю коллекцию, чтобы обеспечить эту неизменность. Простейшим примером является стек, реализованный в виде односвязного списка, и операции push/pop. Вы можете повторно использовать все узлы из предыдущей коллекции в новой коллекции, добавив только один узел для push и не клонировав ни одного узла для pop. Операция push_tail в односвязном списке, с другой стороны, не так проста и эффективна.

Неизменяемые и изменчивые переменные/ссылки

Некоторые функциональные языки используют концепцию неизменности для самих ссылок на объекты, допуская только одно присвоение ссылок.

  • В Erlang это верно для всех "переменных". Я могу назначить объекты только один раз. Если бы я работал с коллекцией, я бы не смог переназначить новую коллекцию на старую ссылку (имя переменной).
  • Scala также встраивает это в язык, когда все ссылки объявляются с помощью var или val, причем vals - это только одно присваивание и продвижение функционального стиля, но vars допускает более C-like или Java структура программы
  • Требуется объявление var/val, в то время как многие традиционные языки используют необязательные модификаторы, такие как final в java и const в C.

Простота разработки и производительность

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

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

Проблемы с производительностью спорны во многих приложениях, но не во всех, именно поэтому многие большие числовые пакеты, такие как класс Numpy Array в Python, допускают обновления больших массивов на месте. Это было бы важно для областей применения, которые используют большие матричные и векторные операции. Эти большие параллельные с данными и вычислительно сложные задачи достигают большого ускорения при работе на месте.

Ответ 3

Отметьте это сообщение в блоге: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html. Это объясняет, почему неизменяемые объекты лучше, чем изменчивые. Короче говоря:

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

Ответ 4

Неизменяемые объекты - очень мощная концепция. Они снимают с себя много бремени, пытаясь поддерживать согласованность объектов/переменных для всех клиентов.

Вы можете использовать их для низкоуровневых неполиморфных объектов, таких как класс CPoint, которые в основном используются с семантикой значений.

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

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

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

Таким образом, неизменяемые объекты, безусловно, являются моим основным способом мышления об объектно-ориентированном дизайне, но они не являются догмой. Они решают много проблем для клиентов объектов, но и создают много, особенно для разработчиков.

Ответ 5

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

Ответ 6

Перемещаемый объект - это просто объект, который может быть изменен после его создания/создания экземпляра, против неизменяемого объекта, который не может быть изменен (см. страница Wikipedia по этому вопросу). Примером этого в языке программирования являются списки и кортежи Pythons. Списки могут быть изменены (например, новые элементы могут быть добавлены после его создания), в то время как кортежи не могут.

Я действительно не думаю, что есть четкий ответ, который лучше для всех ситуаций. У них обоих есть свои места.

Ответ 7

Если тип класса является изменяемым, переменная этого типа класса может иметь несколько разных значений. Например, предположим, что объект foo имеет поле int[] arr, и он содержит ссылку на int[3], содержащую числа {5, 7, 9}. Несмотря на то, что тип поля известен, существует, по крайней мере, четыре разных вещи, которые он может представлять:

  • Потенциально разделяемая ссылка, все владельцы которой заботятся только о том, чтобы она инкапсулировала значения 5, 7 и 9. Если foo хочет arr инкапсулировать разные значения, он должен заменить его другим массив, содержащий нужные значения. Если вы хотите сделать копию foo, можно предоставить копию либо ссылку на arr, либо новый массив, содержащий значения {1,2,3}, в зависимости от того, что удобнее.

  • Единственная ссылка в любом месте юниверса на массив, который инкапсулирует значения 5, 7 и 9. набор из трех мест хранения, которые в настоящий момент удерживают значения 5, 7 и 9; если foo хочет, чтобы он инкапсулировал значения 5, 8 и 9, он может либо изменить второй элемент в этом массиве, либо создать новый массив, содержащий значения 5, 8 и 9, и отказаться от старого. Обратите внимание: если вы хотите сделать копию foo, в копии нужно заменить arr ссылкой на новый массив, чтобы foo.arr оставался единственной ссылкой на этот массив в любой точке юниверса.

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

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

В теории int[] должен быть хорошим простым четко определенным типом, но он имеет четыре очень разных значения. Напротив, ссылка на неизменяемый объект (например, String) обычно имеет только одно значение. Большая часть "власти" неизменных объектов вытекает из этого факта.

Ответ 8

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

Ответ 9

Неизменяемые средства не могут быть изменены, а mutable означает, что вы можете изменить.

Объекты отличаются от примитивов в Java. Примитивы строятся в типах (boolean, int и т.д.), А объекты (классы) - это созданные пользователем типы.

Примитивы и объекты могут быть изменяемыми или неизменяемыми, если они определены как переменные-члены в реализации класса.

Многие люди думают, что примитивы и переменные объекта, имеющие окончательный модификатор infront из них, неизменяемы, однако это не совсем так. Таким образом, окончательный почти не означает неизменяемость переменных. См. Пример здесь
http://www.siteconsortium.com/h/D0000F.php.

Ответ 10

Mutable экземпляры передаются по ссылке.

Необязательные экземпляры передаются по значению.

Абстрактный пример. Предположим, что существует файл с именем txtfile на моем жестком диске. Теперь, когда вы спрашиваете txtfile от меня, я могу вернуть его в двух режимах:

  • Создайте ярлык для txtfile и pas shortcut для вас или
  • Возьмите копию для txtfile и pas copy для вас.

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

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