Изменчивые против неизменных объектов - PullRequest
161 голосов
/ 18 октября 2008

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

Ответы [ 10 ]

153 голосов
/ 18 октября 2008

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

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

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

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

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

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

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

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

25 голосов
/ 18 сентября 2013

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

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

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

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

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

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

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

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

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

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

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

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

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

10 голосов
/ 10 июня 2014

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

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

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

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

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

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

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

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

6 голосов
/ 18 октября 2008

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

4 голосов
/ 18 октября 2008

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

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

1 голос
/ 20 сентября 2013

Если тип класса изменчив, переменная этого типа может иметь несколько различных значений. Например, предположим, что объект 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) обычно имеет только одно значение. Большая часть «силы» неизменных объектов проистекает из этого факта.

0 голосов
/ 31 октября 2015

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

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

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

  1. Создайте ярлык для txtfile и введите ярлык для вас, или
  2. Возьмите копию для txtfile и передайте вам копию.

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

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

0 голосов
/ 01 января 2014

Неизменяемое означает, что нельзя изменить, а изменяемое означает, что вы можете изменить.

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

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

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

0 голосов
/ 17 декабря 2013

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...