Если неизменные объекты хороши, почему люди продолжают создавать изменяемые объекты? [закрыто]


250

Если неизменяемые объекты¹ хороши, просты и дают преимущества в параллельном программировании, почему программисты продолжают создавать изменяемые объекты2?

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


Mm Неизменяемый объект - это объект, состояние которого нельзя изменить после его создания.
² Изменяемый объект - это объект, который можно изменить после его создания.


42
Я думаю , что, кроме законных оснований (как указано Петером ниже), «ленивый разработчик» является более распространенной причиной , чем «тупой» разработчик». И до„глупы разработчиков“есть также„неосведомленные разработчики“.
Joachim Sauer

201
На каждого евангелистского программиста / блоггера приходится 1000 заядлых читателей блогов, которые немедленно заново изобретают себя и применяют новейшие методы. На каждого из них приходится 10 000 программистов, которые прислушиваются к точильному камню, выполняя ежедневную работу и выпуская продукт за дверь. Эти парни используют проверенные и проверенные методы, которые работали на них годами. Они ждут, пока новые методы не получат широкого распространения, и покажут реальные преимущества, прежде чем приступить к их применению. Не называйте их глупыми, и они совсем не ленивы, вместо этого называйте их «занятыми».
Двоичный беспорядок

22
@BinaryWorrier: неизменные объекты вряд ли являются «новой вещью». Возможно, они не использовались для доменных объектов, но в Java и C # они были с самого начала. Кроме того: «ленивый» не всегда плохое слово, некоторые виды «ленивый» являются абсолютным преимуществом для разработчика.
Иоахим Зауэр

11
@Joachim: Я думаю, что совершенно очевидно, что «ленивый» использовался в уничижительном смысле выше :) Кроме того, неизменяемые объекты (такие как Lambda Calculus и OOP в те времена - да, я такой старый) не должны быть новыми внезапно стать ароматом месяца . Я не утверждаю, что они плохие (они не являются), или что у них нет своего места (они, очевидно, делают), просто будьте спокойны с людьми, потому что они не слышали последнее Доброе Слово и обратился как пылкий человек (не обвиняя вас в «ленивом» комментарии, я знаю, что вы пытались смягчить его).
Двоичный беспорядок

116
-1, неизменяемые объекты не являются «хорошими». Просто более или менее подходит для конкретных ситуаций. Любой, кто говорит вам ту или иную технику, объективно «хорош» или «плох» по сравнению с другой в любых ситуациях, продает вам религию.
GrandmasterB

Ответы:


326

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

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

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

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

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


27
Верно. Особенно в программировании GUI, изменяемый объект очень удобен.
Флориан Салихович

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

29
Два небольших предостережения: 1) Возьмите движущегося игрового персонажа. PointКласс в .NET, например неизменен , но создание новых точек , как результат изменения легко и , таким образом , доступный. Анимация неизменяемого персонажа может быть очень дешевой, если отделить «движущиеся части» (но да, тогда некоторые аспекты могут изменяться ). (2) «большие и / или сложные объекты» вполне могут быть неизменными. Струны часто бывают большими и обычно извлекают выгоду из неизменности. Однажды я переписал сложный графовый класс, чтобы он был неизменным, делая код проще и эффективнее. В таких случаях ключевым является наличие изменяемого компоновщика.
Конрад Рудольф

4
@KonradRudolph, хорошие моменты, спасибо. Я не хотел исключать использование неизменяемости в сложных объектах, но правильная и эффективная реализация такого класса далека от тривиальной задачи, и дополнительные усилия не всегда могут быть оправданы.
Петер Тёрёк

6
Вы делаете хорошее замечание о состоянии против личности. Вот почему Рич Хики (автор Clojure) разбил их на части в Clojure. Можно утверждать, что машина, у которой есть 1/2 бака с бензином, отличается от машины с 1/4 бака с бензином. Они имеют одинаковую идентичность, но они не одинаковы, каждый «тик» времени нашей реальности создает клон каждого объекта в нашем мире, тогда наш мозг просто соединяет их вместе с общей идентичностью. У Clojure есть ссылки, атомы, агенты и т. Д. Для представления времени. И карты, векторы и списки для актуального времени.
Тимоти Болдридж

130

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


9
Я знаю, что в большинстве современных IDE для генерации только геттеров требуется практически столько же усилий, сколько и для геттеров и сеттеров. Хотя это правда, что добавление finalи constт. Д. Требует немного дополнительных усилий ... если вы не настроите шаблон кода :-)
Péter Török

10
@ PéterTörök - это не просто дополнительное усилие - это тот факт, что ваши коллеги-программисты захотят повесить вас в образе, потому что они находят ваш стиль кодирования настолько чуждым для их опыта. Это также препятствует такого рода вещам.
Онорио Катеначчи

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

3
@ PéterTörök В императивном языке изменяемые объекты являются поведением по умолчанию. Если вам нужны только неизменяемые объекты, лучше всего перейти на функциональный язык.
Эмори

3
@ PéterTörök Немного наивно полагать, что включение неизменности в программу предполагает не более чем удаление всех сеттеров. Вам по-прежнему нужен способ постепенного изменения состояния программы, и для этого вам нужны компоновщики, неизменяемость эскимо или изменяемые прокси, и все это занимает примерно 1 миллиард раз, затрачиваемое на сброс сеттеров.
Асад Саидуддин

49
  1. Есть место для изменчивости. Принципы доменного управления обеспечивают четкое понимание того, что должно быть изменчивым, а что неизменным. Если вы подумаете об этом, вы поймете, что нецелесообразно представлять себе систему, в которой каждое изменение состояния объекта требует его разрушения и изменения структуры, а также каждого объекта, который на него ссылается. Со сложными системами это может легко привести к полной очистке и восстановлению графов объектов всей системы.

  2. Большинство разработчиков не делают ничего там, где требования к производительности настолько значительны, что им нужно сосредоточиться на параллелизме (или на многих других проблемах, которые повсеместно считаются осведомленными).

  3. Есть некоторые вещи, которые вы просто не можете сделать с неизменяемыми объектами, например, иметь двунаправленные отношения. Как только вы установите значение ассоциации для одного объекта, его идентичность изменится. Итак, вы устанавливаете новое значение для другого объекта, и оно также изменяется. Проблема в том, что ссылка на первый объект больше не действительна, поскольку создан новый экземпляр для представления объекта со ссылкой. Продолжение этого приведет к бесконечным регрессам. После того, как я прочитал ваш вопрос, я немного разобрался с примерами, вот как это выглядит. У вас есть альтернативный подход, который позволяет такую ​​функциональность при сохранении неизменности?

        public class ImmutablePerson { 
    
         public ImmutablePerson(string name, ImmutableEventList eventsToAttend)
         {
              this.name = name;
              this.eventsToAttend = eventsToAttend;
         }
         private string name;
         private ImmutableEventList eventsToAttend;
    
         public string Name { get { return this.name; } }
    
         public ImmutablePerson RSVP(ImmutableEvent immutableEvent){
             // the person is RSVPing an event, thus mutating the state 
             // of the eventsToAttend.  so we need a new person with a reference
             // to the new Event
             ImmutableEvent newEvent = immutableEvent.OnRSVPReceived(this);
             ImmutableEventList newEvents = this.eventsToAttend.Add(newEvent));
             var newSelf = new ImmutablePerson(name, newEvents);
             return newSelf;
         }
        }
    
        public class ImmutableEvent { 
         public ImmutableEvent(DateTime when, ImmutablePersonList peopleAttending, ImmutablePersonList peopleNotAttending){
             this.when = when;     
             this.peopleAttending = peopleAttending;
             this.peopleNotAttending = peopleNotAttending;
         }
         private DateTime when; 
         private ImmutablePersonList peopleAttending;
         private ImmutablePersonList peopleNotAttending;
         public ImmutableEvent OnReschedule(DateTime when){
               return new ImmutableEvent(when,peopleAttending,peopleNotAttending);
         }
         //  notice that this will be an infinite loop, because everytime one counterpart
         //  of the bidirectional relationship is added, its containing object changes
         //  meaning it must re construct a different version of itself to 
         //  represent the mutated state, the other one must update its
         //  reference thereby obsoleting the reference of the first object to it, and 
         //  necessitating recursion
         public ImmutableEvent OnRSVPReceived(ImmutablePerson immutablePerson){
               if(this.peopleAttending.Contains(immutablePerson)) return this;
               ImmutablePersonList attending = this.peopleAttending.Add(immutablePerson);
               ImmutablePersonList notAttending = this.peopleNotAttending.Contains( immutablePerson ) 
                                    ? peopleNotAttending.Remove(immutablePerson)
                                    : peopleNotAttending;
               return new ImmutableEvent(when, attending, notAttending);
         }
        }
        public class ImmutablePersonList
        {
          private ImmutablePerson[] immutablePeople;
          public ImmutablePersonList(ImmutablePerson[] immutablePeople){
              this.immutablePeople = immutablePeople;
          }
          public ImmutablePersonList Add(ImmutablePerson newPerson){
              if(this.Contains(newPerson)) return this;
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length];
              for(var i=0;i<immutablePeople.Length;i++)
                  newPeople[i] = this.immutablePeople[i];
              newPeople[immutablePeople.Length] = newPerson;
          }
          public ImmutablePersonList Remove(ImmutablePerson newPerson){
              if(immutablePeople.IndexOf(newPerson) != -1)
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutablePeople[i] == newPerson;
                 newPeople[i] = this.immutablePeople[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutablePersonList(newPeople);
          }
          public bool Contains(ImmutablePerson immutablePerson){ 
             return this.immutablePeople.IndexOf(immutablePerson) != -1;
          } 
        }
        public class ImmutableEventList
        {
          private ImmutableEvent[] immutableEvents;
          public ImmutableEventList(ImmutableEvent[] immutableEvents){
              this.immutableEvents = immutableEvents;
          }
          public ImmutableEventList Add(ImmutableEvent newEvent){
              if(this.Contains(newEvent)) return this;
              ImmutableEvent[] newEvents= new ImmutableEvent[immutableEvents.Length];
              for(var i=0;i<immutableEvents.Length;i++)
                  newEvents[i] = this.immutableEvents[i];
              newEvents[immutableEvents.Length] = newEvent;
          }
          public ImmutableEventList Remove(ImmutableEvent newEvent){
              if(immutableEvents.IndexOf(newEvent) != -1)
              ImmutableEvent[] newEvents = new ImmutableEvent[immutableEvents.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutableEvents[i] == newEvent;
                 newEvents[i] = this.immutableEvents[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutableEventList(newPeople);
          }
          public bool Contains(ImmutableEvent immutableEvent){ 
             return this.immutableEvent.IndexOf(immutableEvent) != -1;
          } 
        }
    

2
@AndresF., Если вы по-другому подходите к тому, как поддерживать сложные графики с двунаправленными отношениями, используя только неизменяемые объекты, то я бы с удовольствием это услышал. (Я предполагаю, что мы можем согласиться с тем, что коллекция / массив является объектом)
smartcaveman

2
@AndresF., (1) Мое первое утверждение не было универсальным, поэтому оно не было ложным. Я на самом деле предоставил пример кода, чтобы объяснить, как это было обязательно верно в некоторых случаях, которые часто встречаются при разработке приложений. (2) Двунаправленные отношения, как правило, требуют изменчивости. Я не верю, что Java является виновником. Как я уже сказал, я был бы рад оценить любые конструктивные альтернативы, которые вы бы предложили, но в этот момент ваши комментарии звучат так: «Вы ошибаетесь, потому что я так сказал».
smartcaveman

14
@smartcaveman В отношении (2) я также не согласен: в общем, «двунаправленное отношение» - это математическое понятие, ортогональное изменчивости. Как обычно реализовано в Java, это требует изменчивости (я согласился с вами в этом вопросе). Тем не менее, я могу придумать альтернативную реализацию: класс Relationship между двумя объектами с конструктором Relationship(a, b); в момент создания отношений, обе сущности aи bуже существует, и сами отношения тоже неизменны. Я не говорю, что такой подход практичен в Java; просто это возможно.
Андрес Ф.

4
@AndresF., Итак, исходя из того, что вы говорите, если Rесть и то Relationship(a,b)и другое, aи bони неизменны, ни на них, ни на них aне bбудет ссылаться R. Чтобы это работало, ссылка должна храниться где-то еще (например, в статическом классе). Я правильно понимаю ваше намерение?
smartcaveman

9
Можно сохранить двунаправленные отношения для неизменных данных, как указывает Лтх через Чтулху. Вот один из способов сделать это: haskell.org/haskellwiki/Tying_the_Knot
Томас Эдинг

36

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

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

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


3
Это книга Окасаки?
Механическая улитка

Ага. вроде сухо, но тонны хорошей информации ...
Пол Санвальд

4
Забавно, я всегда думал, что красное / черное дерево Окасакиса было намного проще. 10 строк или около того. Я предполагаю, что самое большое преимущество - это когда вы действительно хотите сохранить старую версию.
Томас Ахле

Хотя последнее предложение, вероятно, было верным в прошлом, неясно, останется ли оно верным в будущем, учитывая текущие аппаратные тенденции и т. Д.
jk.

29

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

В настоящее время я изучаю Clojure и имею небольшой опыт работы с Scala (4 года и более на Java), и ваш стиль кодирования меняется из-за осведомленности о состоянии.


Возможно, «известный», а не «популярный» будет лучшим выбором слова.
День

Да это правильно.
Флориан Салихович

5
+1: я согласен: после изучения некоторых Scala и Haskell, я склонен использовать final в Java и const в C ++ везде. Я также использую неизменяемые объекты, если это возможно, и, хотя изменяемые объекты все еще нужны очень часто, просто удивительно, как часто вы можете использовать неизменяемые объекты.
Джорджио

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

13

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


4
+1, шаблон getter / setter используется слишком часто, как своего рода реализация по умолчанию после первого анализа данных.
Яап

Это, наверное, важный момент ... "потому что так делают все остальные" ... так что это должно быть правильно. Для программы «Hello World» это, вероятно, самый простой способ. Управление изменениями состояния объекта с помощью множества изменяемых свойств ... это немного сложнее, чем глубина понимания "Hello World". Спустя 20 лет я очень удивлен, что вершиной программирования 20-го века является написание методов getX и setX (как утомительно) для каждого атрибута объекта без какой-либо структуры. Это только один шаг от прямого доступа к общедоступным свойствам со 100% изменчивостью.
Даррелл Тиг

12

Каждая корпоративная Java-система, над которой я работал в своей карьере, использует либо Hibernate, либо Java Persistence API (JPA). Hibernate и JPA по сути диктуют, что ваша система использует изменяемые объекты, потому что вся их предпосылка в том, что они обнаруживают и сохраняют изменения в ваших объектах данных. Для многих проектов простота разработки, которую приносит Hibernate, более привлекательна, чем преимущества неизменяемых объектов.

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

Но сегодня, если многие начинающие программисты порежут свои зубы на корпоративных системах с использованием Hibernate или другого ORM, то, вероятно, они приобретут привычку использовать изменяемые объекты. Такие фреймворки, как Hibernate, могут укреплять популярность изменчивых объектов.


Отличный момент. Чтобы быть всем для всех, у таких платформ нет другого выбора, кроме как перейти к наименьшему общему знаменателю, использовать прокси на основе отражения и получить / установить свой путь к гибкости. Конечно, это создает системы, в которых практически нет правил перехода между состояниями или имеются общие средства их реализации, к сожалению. После многих проектов я не совсем уверен, что лучше для целесообразности, расширяемости и правильности. Я склонен считать гибрид. Динамическое совершенство ORM, но с некоторыми определениями, какие поля являются обязательными и какие изменения состояния должны быть возможны.
Даррелл Тиг

9

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

Многие программы предназначены для моделирования реальных вещей, которые по своей природе изменчивы. Предположим, что в 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цикл не займет много времени. С другой стороны, использование такой структуры данных значительно увеличит время, необходимое для поиска грузовика конкретного человека. Использование изменяемых объектов с неизменяемыми идентификаторами позволяет избежать этой проблемы.


8

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

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


7

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

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

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


5

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

Есть четыре проблемы, связанные с привязкой данных :

  1. Метаданные отражения конструкторов Java не сохраняют имена аргументов.
  2. Конструкторы Java (и методы) не имеют именованных параметров (также называемых метками), поэтому путают со многими параметрами.
  3. При наследовании другого неизменяемого объекта должен вызываться правильный порядок конструкторов. Это может быть довольно сложно до такой степени, чтобы просто сдаться и оставить одно из полей не финальным.
  4. Большинство технологий связывания (например, Spring MVC Data Binding, Hibernate и т. Д.) Будут работать только с конструкторами по умолчанию без аргументов (это было потому, что аннотации не всегда существовали).

Вы можете уменьшить # 1 и # 2, используя аннотации, такие как @ConstructorPropertiesи создав другой изменяемый объект-конструктор (обычно свободный) для создания неизменяемого объекта.


5

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

Также неизменяемые объекты практически исключают весь класс государственных ошибок.

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

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

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


4

Почему люди используют какие-либо мощные функции? Почему люди используют метапрограммирование, лень или динамическую типизацию? Ответ удобство. Изменчивое состояние так просто. Его так легко обновить на месте, и порог размера проекта, при котором неизменное состояние является более продуктивным для работы, чем изменяемое состояние, достаточно высок, поэтому выбор не надолго вас укусит.


4

Языки программирования предназначены для исполнения компьютерами. Все важные строительные блоки компьютеров - ЦП, ОЗУ, кэш, диски - являются изменяемыми. Когда они не (BIOS), они действительно неизменяемы, и вы не можете создавать новые неизменяемые объекты.

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


1

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

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

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

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

Как PS, я чувствую, что средства получения и установки Java выходят из-под контроля. Проверьте это .


7
Неизменное состояние остается государственным.
Джереми Хейлер

3
@JeremyHeiler: Да, но это состояние чего - то , что является изменяемым. Если ничего не мутирует, то существует только одно состояние, которое то же самое, что и отсутствие состояния вообще.
RalphChapin

1

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

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

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

Многие классы являются изменяемыми после создания, и это нормально, просто не следует напрямую манипулировать его свойствами - вместо этого нужно попросить манипулировать его свойствами с помощью вызовов методов с реальной бизнес-логикой в ​​них (Да, сеттер во многом такой же вещь как непосредственное манипулирование имуществом)

Теперь это на самом деле не относится к коду / языкам стиля «Скриптинг», но к коду, который вы создаете для кого-то другого, и ожидаете, что другие будут многократно читать на протяжении многих лет. В последнее время мне пришлось начать делать это различие, потому что мне так нравится возиться с Groovy, и цели очень сильно различаются.


1

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

У вас не должно быть конструктора с, скажем, шестью параметрами. Вместо этого вы модифицируете объект с помощью методов установки.

Примером этого является объект отчета с установщиками шрифта, ориентации и т. Д.

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

РЕДАКТИРОВАТЬ: шаблон Builder может быть использован для построения всего состояния объекта.


3
это выглядит так, как будто изменчивость и изменения после создания экземпляра - единственный способ установить несколько значений. Для полноты заметим, что шаблон Builder предлагает равные или даже более мощные возможности и не требует жертвовать неизменностью. new ReportBuilder().font("Arial").orientation("landscape").build()
комнат

1
«Было бы непрактично иметь очень длинную сигнатуру конструктора.»: Вы всегда можете сгруппировать параметры в меньшие объекты и передать эти объекты в качестве параметров конструктору.
Джорджио

1
@cHao Что делать, если вы хотите установить 10 или 15 атрибутов? Также не очень хорошо, что метод с именем withFontвозвращает a Report.
Тулаинс Кордова

1
Что касается установки 10 или 15 атрибутов, код для этого не будет неудобным, если (1) Java будет знать, как исключить создание всех этих промежуточных объектов, и если (2) снова, имена будут стандартизированы. Это не проблема неизменности; это проблема с Java, не зная, как это сделать хорошо.
cHao

1
@cHao Создание цепочки вызовов для 20 атрибутов ужасно. Уродливый код имеет тенденцию быть плохим качеством кода.
Тулаинс Кордова

1

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

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

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

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

Итак, почему многие программисты используют изменяемые объекты? ИМХО по двум причинам:

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

1
Проблема не только в скорости. Существует много типов полезных конструкций, которые просто невозможно реализовать без использования изменяемых типов. Помимо прочего, связь между двумя потоками требует, чтобы в абсолютном минимуме оба имели ссылку на общий объект, который можно поместить в данные, а другой - прочитать. Кроме того, часто семантически гораздо яснее сказать «Изменить свойства P и Q этого объекта», чем сказать «Возьмите этот объект, создайте новый объект, который похож на него, за исключением значения P, а затем новый объект, который просто так, за исключением Q ".
суперкат

1
Я не эксперт по FP, но связь между потоками AFAIK (1) может быть достигнута путем создания неизменного значения в одном потоке и чтения его в другом (опять-таки, AFAIK, это подход Эрланга) (2) AFAIK нотация для установки свойства не сильно изменяется (например, в Haskell значение записи setProperty соответствует Java record.setProperty (value)), изменяется только семантика, поскольку результатом установки свойства является новая неизменяемая запись.
Джорджио

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

1
(2) Я уже указал производительность в качестве причины, чтобы не использовать неизменяемое состояние. Что касается (1), конечно, для механизма, который реализует связь между потоками, требуется некоторое изменяемое состояние, но вы можете скрыть это от своей модели программирования, см., Например, модель актора ( en.wikipedia.org/wiki/Actor_model ). Таким образом, даже если на более низком уровне вам нужна изменчивость для реализации связи, вы можете иметь верхний уровень абстракции, на котором вы общаетесь между потоками, отправляющими неизменные объекты туда и обратно.
Джорджио

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

1

Я знаю, что вы спрашиваете о Java, но я все время использую mutable vs immutable в target-c. Существует неизменный массив NSArray и изменяемый массив NSMutableArray. Это два разных класса, написанных специально оптимизированным способом для точного использования. Если мне нужно создать массив и никогда не изменять его содержимое, я бы использовал NSArray, который является меньшим объектом и работает намного быстрее по сравнению с изменяемым массивом.

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

Итак: в зависимости от того, что вы планируете делать с объектом, выбор mutable vs immutable может иметь огромное значение, когда дело доходит до производительности.


1

В дополнение ко многим другим причинам, приведенным здесь, проблема заключается в том, что основные языки плохо поддерживают неизменяемость. По крайней мере, вы наказаны за неизменность в том смысле, что вам нужно добавить дополнительные ключевые слова, такие как const или final, и использовать неразборчивые конструкторы со многими, многими аргументами или шаблонами построителя с длинным кодом.

Это намного проще в языках, созданных с учетом неизменности. Рассмотрим этот фрагмент Scala для определения класса Person, необязательно с именованными аргументами и создания копии с измененным атрибутом:

case class Person(id: String, firstName: String, lastName: String)

val joe = Person("123", "Joe", "Doe")
val spouse = Person(id = "124", firstName = "Mary", lastName = "Moe")
val joeMarried = joe.copy(lastName = "Doe-Moe")

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


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

@gnat Какой из предыдущих ответов говорит о том, что большинство основных языков не поддерживают прилично неизменность? Я думаю, что этот пункт просто не был сделан (я проверил), но это ИМХО довольно важное препятствие.
Ганс-Петер Стёрр

этот, например, углубленно объясняет эту проблему в Java. И как минимум 3 других ответа косвенно относятся к этому
комнат

0

Неизменяемый означает, что вы не можете изменить значение, а изменяемый означает, что вы можете изменить значение, если вы думаете с точки зрения примитивов и объектов. Объекты отличаются от примитивов в Java тем, что они встроены в типы. Примитивы построены в таких типах, как int, boolean и void.

Многие люди думают, что переменные примитивов и объектов, которые имеют заключительный модификатор перед ними, неизменны, однако это не совсем так. Так что final почти не означает неизменяемость для переменных. Проверьте эту ссылку для примера кода:

public abstract class FinalBase {

    private final int variable; // Unset

    /* if final really means immutable than
     * I shouldn't be able to set the variable
     * but I can.
     */
    public FinalBase(int variable) { 
        this.variable = variable;
    }

    public int getVariable() {
        return variable;
    }

    public abstract void method();
}

// This is not fully necessary for this example
// but helps you see how to set the final value 
// in a sub class.
public class FinalSubclass extends FinalBase {

    public FinalSubclass(int variable) {
        super(variable);
    }

    @Override
    public void method() {
        System.out.println( getVariable() );
    }

    @Override
    public int getVariable() {

        return super.getVariable();
    }

    public static void main(String[] args) {
        FinalSubclass subclass = new FinalSubclass(10);
        subclass.method();
    }
}

-1

Я думаю, что хорошей причиной было бы моделировать «реальные» изменчивые «объекты», такие как интерфейсные окна. Я смутно помню, что читал, что ООП был изобретен, когда кто-то пытался написать программное обеспечение для управления работой грузового порта.


1
Я пока не могу найти, где я читал об источниках ООП, но, согласно Википедии , в Smalltalk написана некая интегрированная региональная информационная система крупной компании по перевозке контейнеров OOCL .
Алексей

-2

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


5
finalна самом деле около 1/4 шага к неизменности. Не видя, почему вы упоминаете это.
Цао

ты на самом деле читал мой пост?
Ральф Х

Вы действительно прочитали вопрос? :) Это не имеет ничего общего с final. Поднятие этого в этом контексте вызывает действительно странное смешение finalс «изменчивым», когда самой finalцелью является предотвращение определенных видов мутаций. (КСТАТИ, не мое downvote.)
cHao

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

1
Есть на самом деле очень мало случаев , когда это изменяемый объект , необходимые . Есть случаи, когда код будет более понятным или, в некоторых случаях, быстрее, используя изменяемые объекты. Но учтите, что подавляющее большинство шаблонов проектирования - это всего лишь недооцененное исполнение функционального программирования для языков, которые его изначально не поддерживают. Самое интересное, что FP требует изменчивости только в очень избранных местах (а именно там, где должны возникать побочные эффекты), и эти места обычно сильно ограничены по количеству, размеру и объему.
cHao
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.