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

StackOverflow https://stackoverflow.com/questions/622664

  •  05-07-2019
  •  | 
  •  

Вопрос

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

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

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

Каково именно определение состояния в этом примере?Государство — это концепция, которую я еще не совсем уловил.

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

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

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

Решение

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

  • Неизменяемость применяется в первую очередь к объектам (строкам, массивам, пользовательскому классу Animal).
  • Обычно, если существует неизменяемая версия класса, доступна также и изменяемая версия.Например, 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 является неизменяемой.Таким образом, неизменяемые объекты могут помочь обеспечить соблюдение хороших практик объектно-ориентированного программирования.

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

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

Состояние

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

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

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

Неизменность

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

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

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

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

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

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

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

Государственные

Состояние приложения можно просто представить как содержимое всей памяти и регистров ЦП в данный момент времени.

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

<Ол>
  • Состояние кучи
  • Состояние стека каждого исполняющего потока
  • В управляемых средах, таких как C # и Java, один поток не может получить доступ к памяти другого. Следовательно, каждый поток «владеет» состоянием своего стека. Стек можно рассматривать как содержащий локальные переменные и параметры типа значения ( struct ), а также ссылки на объекты. Эти значения изолированы от внешних потоков.

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

    В ООП состояние экземпляра класса определяется его полями. Эти поля хранятся в куче и поэтому доступны из всех потоков. Если класс определяет методы, позволяющие изменять поля после завершения работы конструктора, тогда этот класс является изменяемым (не неизменяемым). Если поля не могут быть изменены каким-либо образом, то тип является неизменным. Важно отметить, что класс со всеми полями C # readonly / Java final не обязательно является неизменным. Эти конструкции гарантируют, что reference не может измениться, но не объект ссылки. Например, поле может иметь неизменную ссылку на список объектов, но фактическое

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

    Когда вы изменяете строку, вы просто получаете новую строку. Исходная строка не изменится. У 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: вопрос о боксе

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

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

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

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

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

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

    " ... зачем мне об этом беспокоиться? "

    Практическим примером является повторяющаяся конкатенация строк. Например, в .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 так важно.

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

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

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

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

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

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

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

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

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

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

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

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

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

    <Ол>
  • Они менее подвержены ошибкам и более безопасны.

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

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

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

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

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

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

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

  • Более подробный ответ смотрите в моем блоге:
    http://javaexplorer03.blogspot.in/2015/07/minimize-mutability. HTML

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

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

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

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

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

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

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

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

    РЕДАКТИРОВАТЬ: Immutable = Невозможно назначить, если оно содержит некоторое значение & amp; не может быть заменено на месте (может быть?)

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

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

    Лично я хотел бы, чтобы понятие неизменности было проще выразить на языке, который я использую чаще всего, C #. Для полей доступен модификатор 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');
    

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

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

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