Что такое неизменность и почему я должен беспокоиться об этом?


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

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

Я упоминал в прошлом потоке, что я думал, что неизменность-это процесс создания объекта только для чтения и придания ему низкой видимости. Другой участник сказал, что это действительно не имеет никакого отношения к этому. этот страница (часть a серия) использует пример неизменяемого класса / структуры и использует только чтение и другие понятия для его блокировки.

Что именно является определением состояния в случае этого примера? Государство-это понятие, которое я на самом деле не понял.

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

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

15 55

15 ответов:

Что такое неизменность?

  • неизменяемость применяется в основном к объектам (строки, массивы, пользовательский класс животных)
  • как правило, если существует неизменяемая версия класса, также доступна изменяемая версия. Например, Objective-C и Cocoa определяют как класс NSString (неизменяемый), так и класс NSMutableString.
  • если объект является неизменяемым, он не может быть изменен после его создания (в основном только для чтения). Вы могли бы думать об этом как "только конструктор может изменить объект".

это не имеет прямого отношения к вводу пользователем; даже ваш код не может изменить значение неизменяемого объекта. Однако вы всегда можете создать новый неизменяемый объект для его замены. Вот пример псевдокода; обратите внимание, что во многих языках вы можете просто сделать myString = "hello"; вместо того, чтобы использовать конструктор, как я сделал ниже, но я включил ее для ясности:

String myString = new ImmutableString("hello");
myString.appendString(" world"); // Can't do this
myString.setValue("hello world"); // Can't do this
myString = new ImmutableString("hello world"); // OK

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

преимущества неизменности

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

    String myString = "HeLLo WoRLd";
    String lowercasedString = lowercase(myString);
    print myString + " was converted to " + lowercasedString;
    

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

  • проще сделать неизменяемый объект потокобезопасным

  • это потенциально упрощает реализацию класса (хорошо, если вы тот, кто пишет класс)

State

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

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

неизменяемости

проще говоря, память неизменна, когда она не изменяется после инициализации.

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

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

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

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

многие функциональные языки программирования, такие как Lisp, Haskell, Erlang, F# и Clojure, поощряют неизменяемые структуры данных по самой своей природе. Именно по этой причине они пользуются возрождением интереса по мере того, как мы продвигаемся к все более сложной многопоточной разработке приложений и многокомпьютерным компьютерным архитектурам.

State

состояние приложения можно просто думать о содержимом всех регистров памяти и процессора в данный момент времени.

логически состояние программы можно разделить на два:

  1. состояние "кучи"
  2. состояние стека каждого потока

в управляемых средах, таких как C# и Java, один поток не может получить доступ к памяти другого. Поэтому каждый поток "владеет" состоянием своего стека. Стек можно рассматривать как проведение локальных переменных и параметров типа значения (struct), и ссылки на объекты. Эти значения изолированы от внешних потоков.

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

в ООП состояние экземпляра класса определяется его полями. Эти поля хранятся в куче и поэтому доступны из всех нитей. Если класс определяет методы, которые позволяют изменять поля после завершения конструктора, то класс является изменяемым (не неизменяемым). Если поля не могут быть изменены каким-либо образом, то тип является неизменяемым. Важно отметить, что класс с изменяемым полем не обязательно является неизменяемым. Например, в C#, просто потому что поле типа List<object> определяется как readonly, фактическое содержание списка может быть изменено в любое время.

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

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

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

проще говоря: как только вы создаете неизменяемый объект, нет никакого способа изменить содержимое этого объекта. Примерами неизменяемых объектов .Net являются String и Uri.

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

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

надеюсь, что это помогает.

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

например вот карта в Clojure

(def imap {1 "1" 2 "2"})
(conj imap [3 "3"])
(println imap)

первая строка создает новую неизменяемую карту Clojure. Вторая строка соединяет 3 и "3" на карте. Это может показаться, как будто он изменяет старую карту, но на самом деле он возвращает новая карта с добавлением 3 "3". Это яркий пример неизменности. Если бы это была изменчивая карта, она просто добавила бы 3 "3" напрямую до та же старая карта. Третья строка печатает карту

{3 "3", 1 "1", 2 "2"}

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

хороший вопрос.

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

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

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

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

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

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

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

  • объекты значения (иногда также известные как объекты данных или pojo)
  • структуры (в C# / .NET) - см. stackoverflow вопрос о боксе

делая вещи неизменными предотвращает большое количество распространенных ошибок.

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

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

создание неизменяемых вещей означает, что вам не нужно запоминать (или тратить время/память), чтобы сделать защитные копии.

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

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

"... почему я должен беспокоиться об этом?"

практическим примером является повторяющаяся конкатенация строк. В .NET например:

string SlowStringAppend(string [] files)
{
    // Declare an string
    string result="";

    for (int i=0;i<files.length;i++)
    {
        // result is a completely new string equal to itself plus the content of the new
        // file
        result = result + File.ReadAllText(files[i]);
    }

    return result;
}    

string EfficientStringAppend(string [] files)
{
    // Stringbuilder manages a internal data buffer that will only be expanded when absolutely necessary
    StringBuilder result=new SringBuilder();

    for (int i=0;i<files.length;i++)
    {
        // The pre-allocated buffer (result) is appended to with the new string 
        // and only expands when necessary.  It doubles in size each expansion
        // so need for allocations become less common as it grows in size. 
        result.Append(File.ReadAllText(files[i]));
    }

    return result.ToString();
}

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

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

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

string mystring = "inital value";
mystring = "new value";
System.Console.WriteLine(mystring); // Outputs "new value";

а сами подумайте, - но я его меняю, смотрите прямо здесь, в черно-белом! новое значение выходов mystring совсем''...... Я думал, ты сказал, что я не могу изменить это?!!"

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

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

одно из мест, где это действительно выдувает re:использование памяти находится в очень итеративных циклах, особенно со строками, как в сообщении Ashs. Скажем, вы строили HTML-страницу в итерационном цикле, где вы постоянно добавляли следующий HTML-блок к последнему и, просто для пинки, вы делали это на сервере большого объема. Это постоянное выделение " новой памяти значений "может быстро стать дорогостоящим и в конечном итоге фатальным, если" старая память значений " не будет должным образом очищена.

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

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

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

Как и в сообщении Ashs, в .Net и со строками, рекомендуется использовать изменяемый класс StringBuilder, а не неизменяемые классы строк, когда дело доходит до нужно постоянно менять значение строки.

другие языки / типы также будут иметь свои собственные обходные пути.

Почему Неизменяемость?

  1. Они менее склонны к ошибкам и более безопасны.

  2. неизменяемые классы легче проектировать, реализовывать и использовать, чем изменяемые классы.

  3. неизменяемые объекты потокобезопасны, поэтому нет проблем с синхронизацией.

  4. неизменяемые объекты-это хорошие ключи карты и элементы набора, поскольку они обычно не меняются один раз создан.

  5. неизменяемость упрощает написание, использование и рассуждение о коде (инвариант класса устанавливается один раз, а затем остается неизменным).

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

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

  8. ссылки на неизменяемые объекты могут быть кэшированы, так как они не изменится.(т. е. в хэшировании он обеспечивает быстрые операции).

смотрите мой блог для более подробной информации answer:
http://javaexplorer03.blogspot.in/2015/07/minimize-mutability.html

Послушайте, я не читал ссылки, которые вы разместили.

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

переменные (значения, которые изменяются) сохраняются для поддержания состояния. Неизменяемый означает некоторые данные, которые не меняются. Вы можете сказать, что это то же самое, что и readonly или constant в некотором роде (это можно увидеть таким образом).

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

.net имеет класс string, который является примером.
т. е. вы не можете изменить строку на ее месте

строка s = "привет"; Я могу написать s. Replace ("el", "a"); но это не изменит содержимое переменной s.

Что я могу сделать, это S = S.заменить ("el", "a");
Эта воля создайте новую переменную и назначьте ее значение s (перезапись содержимого s).

эксперты могут исправить ошибки, если у меня есть, в моем понимании.

изменить: неизменяемые = запрещает использовать ботов после того, как он держит какую-то ценность и не может быть заменен на месте (возможно?)

пример потенциальных преимуществ производительности, предлагаемых неизменяемыми объектами, доступен в API WPF. Общим базовым классом многих типов WPF является Freezable.

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

лично я хотел бы, чтобы понятие неизменности было легче выразить на языке, который я использую чаще всего, C#. Есть readonly модификатор доступен для полей. Я хотел бы увидеть readonly модификатор на типах, а это было бы разрешено только для типов, которые имеют только поля readonly, которые имеют только типы readonly. По существу это означает, что все состояние должно быть введено во время построения, и что и весь граф объекта будет заморожен. Я предполагаю, что если бы эти метаданные были присущи CLR, то их можно было бы легко использовать для оптимизации анализа мусора для GC.

Извините, почему неизменность предотвращает условия гонки (в этом примере запись после чтения опасностей)?

shared v = Integer(3)
v = Integer(v.value() + 1) # in parallel

неизменность - это ценности, а ценности-это факты. Что-то имеет значение, если оно неизменяемо, потому что если что-то может быть изменено, то это означает, что никакое конкретное значение не может быть связано с ним. Объект был инициализирован с состоянием A и во время выполнения программы был мутирован в состояние B и состояние C. Это означает, что объект не представляет собой одно конкретное значение, а является только контейнером, абстракцией на месте в памяти, не более того. Вы не можете доверять такому контейнеру, вы не можете поверьте, что этот контейнер имеет значение, которое вы предполагаете, должно иметь.

перейдем к примеру-представим, что в коде создается экземпляр класса Book.

Book bookPotter =  new Book();
bookPotter.setAuthor('J.K Rowling');
bookPotter.setTitle('Harry Potter');

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

Book bookLor =  bookPotter; // only reference pass
bookLor.setAuthor('J.R.R Tolkien');
bookLor.setTitle('Lords of The Rings');

не обманывайте себя другим именем переменной, на самом деле это один и тот же экземпляр. Код снова использует сеттеры на том же экземпляре. Это значит, что bookPotter никогда не был действительно книгой Гарри Поттера, bookPotter-это только указатель на место, где находится Неизвестная книга. Тем не менее, похоже, что это больше полка, чем книга. Какое доверие можно иметь к такому объекту? Это книга Гарри Поттера или ЛОР книга или ни то, ни другое?

изменяемый экземпляр класса является только указателем на неизвестное состояние с характеристиками класса.

как же тогда избежать мутации? Это довольно легко в правила:

  • построить объект с желаемым состоянием с помощью конструктора или строителя
  • не создавайте сеттеры для инкапсулированного состояния объекта
  • не изменяйте инкапсулированное состояние объекта ни в одном из его методов

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

Book bookPotter =  new Book('J.K Rowling', 'Harry Potter');
Book bookLor = new Book('J.R.R Tolkien', 'Lord of The Rings');

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

Если вас интересует более широкая область неизменяемости, в этой средней статье больше о предмете в отношении JavaScript -https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310.