Вопрос

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

Это было полезно?

Решение

В этом есть пара аспектов. Во-первых, изменяемые объекты без ссылочной идентичности могут вызывать ошибки в нечетное время. Например, рассмотрим бин 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) , Короче говоря, преимущества в некоторой степени зависят от вашей проблемы, но я бы предпочел неизменность.

Другие советы

Неизменяемые объекты противНеизменяемые коллекции

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

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

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

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

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

Неизменяемый противИзменяемые переменные / ссылки

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top