Если неизменяемые объекты¹ хороши, просты и дают преимущества в параллельном программировании, почему программисты продолжают создавать изменяемые объекты2?
У меня есть четыре года опыта в программировании на Java, и, как я понимаю, первое, что люди делают после создания класса, - это генерирование геттеров и сеттеров в IDE (что делает его изменчивым). Есть ли недостаток осведомленности, или мы можем избежать использования изменяемых объектов в большинстве сценариев?
Mm Неизменяемый объект - это объект, состояние которого нельзя изменить после его создания.
² Изменяемый объект - это объект, который можно изменить после его создания.
object-oriented
immutability
ahsteele
источник
источник
Ответы:
Как изменяемые, так и неизменные объекты имеют свои собственные преимущества, плюсы и минусы.
Неизменяемые объекты действительно облегчают жизнь во многих случаях. Они особенно применимы для типов значений, где объекты не имеют идентичности, поэтому их можно легко заменить. И они могут сделать параллельное программирование более безопасным и чистым (большинство общеизвестно трудных для обнаружения ошибок параллелизма в конечном счете вызвано изменяемым состоянием, разделяемым между потоками). Однако для больших и / или сложных объектов создание новой копии объекта для каждого отдельного изменения может быть очень дорогостоящим и / или утомительным. А для объектов с определенной идентичностью изменение существующих объектов намного проще и интуитивно понятнее, чем создание новой модифицированной копии.
Подумайте об игровом персонаже. В играх скорость является главным приоритетом, поэтому представление игровых персонажей с изменяемыми объектами, скорее всего, заставит вашу игру работать значительно быстрее, чем альтернативная реализация, где новая копия игрового персонажа появляется при каждом небольшом изменении.
Более того, наше восприятие реального мира неизбежно основано на изменчивых объектах. Когда вы заправляете свой автомобиль топливом на заправочной станции, вы все время воспринимаете его как один и тот же объект (т.е. его идентичность сохраняется, пока меняется его состояние ) - не так, как если бы старая машина с пустым баком была заменена на новую последовательно экземпляры автомобилей, имеющих свой танк постепенно, все более и более полный. Таким образом, всякий раз, когда мы моделируем какую-то область реального мира в программе, обычно проще и проще реализовать модель домена, используя изменяемые объекты для представления объектов реального мира.
Помимо всех этих законных причин, увы, наиболее вероятной причиной, по которой люди продолжают создавать изменчивые объекты, является инерция ума, то есть сопротивление переменам. Обратите внимание, что большинство современных разработчиков прошли обучение задолго до того, как неизменность (и содержащая их парадигма, функциональное программирование) стала «модной» в их сфере влияния, и они не обновляют свои знания о новых инструментах и методах нашей торговли - на самом деле, многие из нас, людей, положительно противостоят новым идеям и процессам. «Я программирую , как это для пп- лет , и я не забочусь о последних глупых причудах!»
источник
Point
Класс в .NET, например неизменен , но создание новых точек , как результат изменения легко и , таким образом , доступный. Анимация неизменяемого персонажа может быть очень дешевой, если отделить «движущиеся части» (но да, тогда некоторые аспекты могут изменяться ). (2) «большие и / или сложные объекты» вполне могут быть неизменными. Струны часто бывают большими и обычно извлекают выгоду из неизменности. Однажды я переписал сложный графовый класс, чтобы он был неизменным, делая код проще и эффективнее. В таких случаях ключевым является наличие изменяемого компоновщика.Я думаю, что вы все пропустили самый очевидный ответ. Большинство разработчиков создают изменяемые объекты, потому что изменчивость является обязательной для обязательных языков. У большинства из нас есть лучшее, что можно сделать с нашим временем, чем постоянно изменять код по умолчанию - более корректно или нет. И неизменность не является панацеей больше, чем любой другой подход. Это делает некоторые вещи легче, но делает другие намного сложнее, поскольку некоторые ответы уже указали.
источник
final
иconst
т. Д. Требует немного дополнительных усилий ... если вы не настроите шаблон кода :-)Есть место для изменчивости. Принципы доменного управления обеспечивают четкое понимание того, что должно быть изменчивым, а что неизменным. Если вы подумаете об этом, вы поймете, что нецелесообразно представлять себе систему, в которой каждое изменение состояния объекта требует его разрушения и изменения структуры, а также каждого объекта, который на него ссылается. Со сложными системами это может легко привести к полной очистке и восстановлению графов объектов всей системы.
Большинство разработчиков не делают ничего там, где требования к производительности настолько значительны, что им нужно сосредоточиться на параллелизме (или на многих других проблемах, которые повсеместно считаются осведомленными).
Есть некоторые вещи, которые вы просто не можете сделать с неизменяемыми объектами, например, иметь двунаправленные отношения. Как только вы установите значение ассоциации для одного объекта, его идентичность изменится. Итак, вы устанавливаете новое значение для другого объекта, и оно также изменяется. Проблема в том, что ссылка на первый объект больше не действительна, поскольку создан новый экземпляр для представления объекта со ссылкой. Продолжение этого приведет к бесконечным регрессам. После того, как я прочитал ваш вопрос, я немного разобрался с примерами, вот как это выглядит. У вас есть альтернативный подход, который позволяет такую функциональность при сохранении неизменности?
источник
Relationship(a, b)
; в момент создания отношений, обе сущностиa
иb
уже существует, и сами отношения тоже неизменны. Я не говорю, что такой подход практичен в Java; просто это возможно.R
есть и тоRelationship(a,b)
и другое,a
иb
они неизменны, ни на них, ни на нихa
неb
будет ссылатьсяR
. Чтобы это работало, ссылка должна храниться где-то еще (например, в статическом классе). Я правильно понимаю ваше намерение?Я читал «чисто функциональные структуры данных», и это заставило меня понять, что существует довольно много структур данных, которые намного проще реализовать с помощью изменяемых объектов.
Чтобы реализовать бинарное дерево поиска, вы должны каждый раз возвращать новое дерево: вашему новому дереву придется делать копию каждого измененного узла (неизмененные ветви являются общими). Для вашей функции вставки это не так уж плохо, но для меня все стало довольно неэффективно, когда я начал работать над удалением и перебалансировкой.
Еще одна вещь, которую нужно понять, это то, что вы можете годами писать объектно-ориентированный код и никогда не понимать, каким ужасным может быть общее изменяемое состояние, если ваш код не запускается таким образом, что это создает проблемы параллелизма.
источник
С моей точки зрения, это недостаток осведомленности. Если вы посмотрите на другие известные языки JVM (Scala, Clojure), изменяемые объекты редко встречаются в коде, и поэтому люди начинают использовать их в сценариях, где однопоточности недостаточно.
В настоящее время я изучаю Clojure и имею небольшой опыт работы с Scala (4 года и более на Java), и ваш стиль кодирования меняется из-за осведомленности о состоянии.
источник
Я думаю , что один из основных факторов были проигнорированы: Java Beans в значительной степени зависит от конкретного стиля мутируют объекты, и (особенно с учетом источника) довольно много людей , кажется, считать , что в качестве (или даже в ) каноническом примере того , как все Java должно быть написано.
источник
Каждая корпоративная Java-система, над которой я работал в своей карьере, использует либо Hibernate, либо Java Persistence API (JPA). Hibernate и JPA по сути диктуют, что ваша система использует изменяемые объекты, потому что вся их предпосылка в том, что они обнаруживают и сохраняют изменения в ваших объектах данных. Для многих проектов простота разработки, которую приносит Hibernate, более привлекательна, чем преимущества неизменяемых объектов.
Очевидно, что изменяемые объекты существуют намного дольше, чем Hibernate, поэтому Hibernate, вероятно, не является первопричиной популярности изменяемых объектов. Возможно, популярность изменчивых объектов позволила Hibernate процветать.
Но сегодня, если многие начинающие программисты порежут свои зубы на корпоративных системах с использованием Hibernate или другого ORM, то, вероятно, они приобретут привычку использовать изменяемые объекты. Такие фреймворки, как Hibernate, могут укреплять популярность изменчивых объектов.
источник
Важным моментом, который еще не упоминался, является то, что наличие изменяемого состояния объекта позволяет иметь неизменяемость идентификатора объекта, который инкапсулирует это состояние.
Многие программы предназначены для моделирования реальных вещей, которые по своей природе изменчивы. Предположим, что в 12:51 некоторая переменная
AllTrucks
содержит ссылку на объект # 451, который является корнем структуры данных, которая указывает, какой груз содержится на всех грузовиках парка в этот момент (12:51 утра), и некоторую переменнуюBobsTruck
может использоваться для получения ссылки на объект № 24601, указывающей на объект, который указывает, какой груз содержится в грузовике Боба в тот момент (0:51 утра). В 12:52 некоторые грузовики (включая Боба) загружаются и выгружаются, и структуры данных обновляются, так чтоAllTrucks
теперь будет храниться ссылка на структуру данных, которая указывает, что груз находится во всех грузовиках по состоянию на 0:52.Что должно случиться
BobsTruck
?Если свойство «Cargo» каждого объекта грузовика является неизменным, тогда объект № 24601 будет всегда представлять состояние, в котором находился грузовик Боба в 0:51. Если
BobsTruck
содержит прямую ссылку на объект # 24601, то, если обновленный кодAllTrucks
также не обновляетсяBobsTruck
, он перестанет представлять текущее состояние грузовика Боба. Отметим далее, что, если только онBobsTruck
не хранится в какой-либо форме изменяемого объекта, единственный код, которыйAllTrucks
может обновлять обновленный код, - это если бы код был явно запрограммирован для этого.Если кто-то хочет иметь возможность
BobsTruck
наблюдать состояние грузовика Боба, сохраняя при этом все объекты неизменяемыми, он можетBobsTruck
быть неизменной функцией, которая, учитывая значение, котороеAllTrucks
имеет или имела в любой конкретный момент времени, даст состояние грузовика Боба в то время. Можно даже иметь в нем пару неизменяемых функций, одна из которых будет такой же, как и выше, а другая будет принимать ссылку на состояние флота и новое состояние грузовика и возвращать ссылку на новое состояние флота, которое соответствует старому, за исключением того, что у грузовика Боба будет новое состояние.К сожалению, необходимость использовать такую функцию каждый раз, когда кто-то хочет получить доступ к состоянию грузовика Боба, может стать довольно раздражающей и громоздкой. Альтернативный подход заключается в том, чтобы сказать, что объект № 24601 всегда и всегда (до тех пор, пока кто-либо имеет ссылку на него) представляет текущее состояние грузовика Боба. Код, который будет хотеть многократно обращаться к текущему состоянию грузовика Боба, не должен будет каждый раз запускать некоторую трудоемкую функцию - он может просто один раз выполнить функцию поиска, чтобы выяснить, что объект # 24601 является грузовиком Боба, а затем просто доступ к этому объекту в любое время, когда он хочет увидеть текущее состояние грузовика Боба.
Обратите внимание, что функциональный подход не лишен преимуществ в однопоточной среде или в многопоточной среде, где потоки будут в основном просто наблюдать за данными, а не изменять их. Любой поток наблюдателя, который копирует ссылку на объект, содержащийся в
AllTrucks
а затем исследует состояния грузовиков, представленных таким образом, увидит состояние всех грузовиков на момент, когда он захватил ссылку. Каждый раз, когда поток наблюдателя хочет увидеть новые данные, он может просто повторно захватить ссылку. С другой стороны, наличие всего состояния парка, представленного одним неизменным объектом, исключает возможность одновременного обновления двумя грузовиками двух потоков, поскольку каждый поток, если оставить его для своих собственных устройств, будет создавать новый объект «состояние парка», который включает новое состояние его грузовика и старые состояния друг друга. Корректность может быть гарантирована, если каждый поток используетCompareExchange
для обновления,AllTrucks
только если он не изменился, и отвечает на сбойCompareExchange
путем регенерации объекта состояния и повторения операции, но если более чем один поток пытается выполнить операцию одновременной записи, производительность, как правило, будет хуже, чем если бы все записи выполнялись в одном потоке; чем больше потоков пытается выполнить такие одновременные операции, тем хуже будет производительность.Если отдельные объекты грузовика изменчивы, но имеют неизменные идентификаторы , многопоточный сценарий становится чище. Только одна резьба может работать одновременно на любом грузовике, но нити, работающие на разных грузовиках, могут работать без помех. Хотя есть способы, которыми можно эмулировать такое поведение даже при использовании неизменяемых объектов (например, можно определить объекты «AllTrucks» так, чтобы для установки состояния грузовика, принадлежащего XXX, к SSS просто потребовалось бы создать объект, который говорит «По состоянию на [Время], состояние грузовика, принадлежащего [XXX], теперь равно [SSS]; состояние всего остального - [Старое значение AllTrucks] ". Создание такого объекта было бы достаточно быстрым, чтобы даже при наличии раздора
CompareExchange
цикл не займет много времени. С другой стороны, использование такой структуры данных значительно увеличит время, необходимое для поиска грузовика конкретного человека. Использование изменяемых объектов с неизменяемыми идентификаторами позволяет избежать этой проблемы.источник
Там нет правильного или неправильного, это просто зависит от того, что вы предпочитаете. Есть причина, по которой некоторые люди предпочитают языки, которые предпочитают одну парадигму другой, а одну модель данных - другой. Это зависит только от ваших предпочтений и от того, чего вы хотите достичь (а возможность легко использовать оба подхода без отчуждения преданных поклонников той или иной стороны - это святой Грааль, к которому стремятся некоторые языки).
Я думаю, что лучший и самый быстрый способ ответить на ваш вопрос - это обсудить преимущества и недостатки неизменяемости и изменчивости .
источник
Проверьте это сообщение в блоге: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Это суммирует, почему неизменяемые объекты лучше, чем изменяемые. Вот краткий список аргументов:
Насколько я понимаю, люди используют изменяемые объекты, потому что они все еще смешивают ООП с императивным процедурным программированием.
источник
В Java неизменяемые объекты требуют конструктора, который будет принимать все свойства объекта (или конструктор создает их из других аргументов или значений по умолчанию). Эти свойства должны быть помечены как окончательные .
Есть четыре проблемы, связанные с привязкой данных :
Вы можете уменьшить # 1 и # 2, используя аннотации, такие как
@ConstructorProperties
и создав другой изменяемый объект-конструктор (обычно свободный) для создания неизменяемого объекта.источник
Я удивлен, что никто не упомянул преимущества оптимизации производительности. В зависимости от языка компилятор может сделать кучу оптимизаций при работе с неизменяемыми данными, потому что он знает, что данные никогда не изменятся. Все виды вещей пропускаются, что дает вам огромный выигрыш в производительности.
Также неизменяемые объекты практически исключают весь класс государственных ошибок.
Он не такой большой, как должен быть, потому что он сложнее, не относится ко всем языкам, и большинство людей обучалось императивному кодированию.
Также я обнаружил, что большинство программистов довольны своей коробкой и часто сопротивляются новым идеям, которые они не совсем понимают. Вообще люди не любят перемен.
Также помните, что состояние большинства программистов плохое. Большинство программ, которые проводятся в дикой природе, ужасны, и это вызвано отсутствием понимания и политики.
источник
Почему люди используют какие-либо мощные функции? Почему люди используют метапрограммирование, лень или динамическую типизацию? Ответ удобство. Изменчивое состояние так просто. Его так легко обновить на месте, и порог размера проекта, при котором неизменное состояние является более продуктивным для работы, чем изменяемое состояние, достаточно высок, поэтому выбор не надолго вас укусит.
источник
Языки программирования предназначены для исполнения компьютерами. Все важные строительные блоки компьютеров - ЦП, ОЗУ, кэш, диски - являются изменяемыми. Когда они не (BIOS), они действительно неизменяемы, и вы не можете создавать новые неизменяемые объекты.
Следовательно, любой язык программирования, построенный поверх неизменяемых объектов, страдает недостатком представления в своей реализации. А для ранних языков, таких как C, это был большой камень преткновения.
источник
Без изменяемых объектов у вас нет состояния. Следует признать, что это хорошая вещь, если вы можете управлять ею и если есть вероятность, что на объект можно ссылаться из более чем одного потока. Но программа будет довольно скучной. Многие программы, в частности веб-серверы, избегают брать на себя ответственность за изменяемые объекты, отталкивая изменчивость в базах данных, операционных системах, системных библиотеках и т. Д. На практике это освобождает программиста от проблем с изменяемостью и делает сеть (и другую) Разработка возможна. Но изменчивость все еще там.
В целом, у вас есть три типа классов: обычные, не ориентированные на многопотоковое исполнение классы, которые необходимо тщательно охранять и защищать; неизменяемые классы, которые можно использовать свободно; и изменяемые, потокобезопасные классы, которые могут использоваться свободно, но которые должны быть написаны с особой тщательностью. Первый тип - проблемный, а худшие - это те, которые, как считается , относятся к третьему типу. Конечно, первый тип - самый простой для написания.
Я обычно заканчиваю множеством нормальных, изменчивых классов, за которыми я должен очень внимательно следить. В ситуации с несколькими потоками необходимая синхронизация замедляет все, даже когда я могу избежать смертельных объятий. Поэтому я обычно делаю неизменные копии изменяемого класса и передаю их тем, кто может их использовать. Каждый раз, когда мутирует оригинал, требуется новая неизменяемая копия, поэтому я думаю, что иногда у меня может быть сто копий оригинала. Я полностью зависим от сбора мусора.
Подводя итог, можно сказать, что не-поточно-изменяемые объекты хороши, если вы не используете несколько потоков. (Но многопоточность распространяется везде - будьте осторожны!) В противном случае их можно безопасно использовать, если вы ограничите их локальными переменными или строго синхронизируете их. Если вы можете избежать их, используя проверенный код других людей (БД, системные вызовы и т. Д.), Сделайте это. Если вы можете использовать неизменный класс, сделайте это. И я думаю , что в целом люди либо не знают о проблемах с многопоточностью, либо (разумно) боятся их и используют всевозможные уловки, чтобы избежать многопоточности (или, скорее, возлагают ответственность за это в других местах).
Как PS, я чувствую, что средства получения и установки Java выходят из-под контроля. Проверьте это .
источник
У многих людей были хорошие ответы, поэтому я хочу указать на то, что вы затронули, было очень наблюдательным и чрезвычайно правдивым, и нигде здесь не упоминалось.
Автоматическое создание сеттеров и геттеров - ужасная, ужасная идея, но это первый способ, когда процедурные люди пытаются навязать ОО в своем мышлении. Сеттеры и геттеры вместе со свойствами должны создаваться только тогда, когда они вам нужны, а не по умолчанию
На самом деле, хотя вам нужны геттеры довольно регулярно, единственный способ, которым сеттеры или доступные для записи свойства должны когда-либо существовать в вашем коде, - это через шаблон компоновщика, где они блокируются после полной реализации объекта.
Многие классы являются изменяемыми после создания, и это нормально, просто не следует напрямую манипулировать его свойствами - вместо этого нужно попросить манипулировать его свойствами с помощью вызовов методов с реальной бизнес-логикой в них (Да, сеттер во многом такой же вещь как непосредственное манипулирование имуществом)
Теперь это на самом деле не относится к коду / языкам стиля «Скриптинг», но к коду, который вы создаете для кого-то другого, и ожидаете, что другие будут многократно читать на протяжении многих лет. В последнее время мне пришлось начать делать это различие, потому что мне так нравится возиться с Groovy, и цели очень сильно различаются.
источник
Изменчивые объекты используются, когда вам нужно установить кратные значения после создания объекта.
У вас не должно быть конструктора с, скажем, шестью параметрами. Вместо этого вы модифицируете объект с помощью методов установки.
Примером этого является объект отчета с установщиками шрифта, ориентации и т. Д.
Вкратце: переменные полезны, когда у вас есть много состояний для установки на объект, и было бы непрактично иметь очень длинную сигнатуру конструктора.
РЕДАКТИРОВАТЬ: шаблон Builder может быть использован для построения всего состояния объекта.
источник
new ReportBuilder().font("Arial").orientation("landscape").build()
withFont
возвращает aReport
.Я думаю, что использование изменяемых объектов проистекает из императивного мышления: вы вычисляете результат, изменяя содержание изменяемых переменных шаг за шагом (вычисление побочным эффектом).
Если вы мыслите функционально, вы хотите иметь неизменное состояние и представлять последующие состояния системы, применяя функции и создавая новые значения из старых.
Функциональный подход может быть более чистым и более устойчивым, но он может быть очень неэффективным из-за копирования, так что вы захотите вернуться к общей структуре данных, которую вы изменяете постепенно.
Компромисс, который я считаю наиболее разумным: начинайте с неизменяемых объектов, а затем переключайтесь на изменяемые, если ваша реализация недостаточно быстра. С этой точки зрения систематическое использование изменчивых объектов с самого начала можно считать своего рода преждевременной оптимизацией : вы выбираете более эффективную (но также более сложную для понимания и отладки) реализацию с самого начала.
Итак, почему многие программисты используют изменяемые объекты? ИМХО по двум причинам:
источник
Я знаю, что вы спрашиваете о Java, но я все время использую mutable vs immutable в target-c. Существует неизменный массив NSArray и изменяемый массив NSMutableArray. Это два разных класса, написанных специально оптимизированным способом для точного использования. Если мне нужно создать массив и никогда не изменять его содержимое, я бы использовал NSArray, который является меньшим объектом и работает намного быстрее по сравнению с изменяемым массивом.
Таким образом, если вы создаете объект Person, который является неизменным, тогда вам нужны только конструктор и получатели, таким образом, объект будет меньше и будет использовать меньше памяти, что, в свою очередь, сделает вашу программу быстрее. Если вам нужно изменить объект после создания, тогда изменяемый объект Person будет лучше, так что он сможет изменять значения вместо создания нового объекта.
Итак: в зависимости от того, что вы планируете делать с объектом, выбор mutable vs immutable может иметь огромное значение, когда дело доходит до производительности.
источник
В дополнение ко многим другим причинам, приведенным здесь, проблема заключается в том, что основные языки плохо поддерживают неизменяемость. По крайней мере, вы наказаны за неизменность в том смысле, что вам нужно добавить дополнительные ключевые слова, такие как const или final, и использовать неразборчивые конструкторы со многими, многими аргументами или шаблонами построителя с длинным кодом.
Это намного проще в языках, созданных с учетом неизменности. Рассмотрим этот фрагмент Scala для определения класса Person, необязательно с именованными аргументами и создания копии с измененным атрибутом:
Итак, если вы хотите, чтобы разработчики приняли неизменность, это одна из причин, по которой вы можете подумать о переходе на более современный язык программирования.
источник
Неизменяемый означает, что вы не можете изменить значение, а изменяемый означает, что вы можете изменить значение, если вы думаете с точки зрения примитивов и объектов. Объекты отличаются от примитивов в Java тем, что они встроены в типы. Примитивы построены в таких типах, как int, boolean и void.
Многие люди думают, что переменные примитивов и объектов, которые имеют заключительный модификатор перед ними, неизменны, однако это не совсем так. Так что final почти не означает неизменяемость для переменных. Проверьте эту ссылку для примера кода:
источник
Я думаю, что хорошей причиной было бы моделировать «реальные» изменчивые «объекты», такие как интерфейсные окна. Я смутно помню, что читал, что ООП был изобретен, когда кто-то пытался написать программное обеспечение для управления работой грузового порта.
источник
Java во многих случаях требует изменяемых объектов, например, когда вы хотите посчитать или вернуть что-то в посетителе или в runnable, вам нужны конечные переменные, даже без многопоточности.
источник
final
на самом деле около 1/4 шага к неизменности. Не видя, почему вы упоминаете это.final
. Поднятие этого в этом контексте вызывает действительно странное смешениеfinal
с «изменчивым», когда самойfinal
целью является предотвращение определенных видов мутаций. (КСТАТИ, не мое downvote.)