Отменяемые и неизменяемые объекты

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

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

     Map map = ... Person p = new Person(); map.put(p, "Hey, there!"); p.setName("Daniel"); map.get(p); // => null 

    Экземпляр Person получает «потерянный» на карте при использовании в качестве ключа, потому что это hashCode и равенство основаны на изменяемых значениях. Эти значения изменились за пределами карты, и все хеширование стало устаревшим. Теоретики любят спорить по этому поводу, но на практике я не считаю, что это слишком большая проблема.

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

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

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

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

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

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

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

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

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

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

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

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

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

    Простота развития и производительность

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Если тип classа является изменяемым, переменная этого типа classа может иметь несколько разных значений. Например, предположим, что у объекта foo есть поле int[] arr , и оно содержит ссылку на int[3] содержащую числа {5, 7, 9}. Несмотря на то, что тип поля известен, существует, по крайней мере, четыре разных вещи, которые он может представлять:

    • Потенциально общая ссылка, все владельцы которой заботятся только о том, что она инкапсулирует значения 5, 7 и 9. Если foo хочет, чтобы arr инкапсулировал разные значения, он должен заменить его другим массивом, который содержит нужные значения. Если вы хотите сделать копию foo , можно предоставить копию либо ссылку на arr либо новый массив, содержащий значения {1,2,3}, в зависимости от того, что более удобно.

    • Единственная ссылка в любом месте юниверса на массив, который инкапсулирует значения 5, 7 и 9. набор из трех мест хранения, которые в настоящий момент удерживают значения 5, 7 и 9; если foo хочет, чтобы он инкапсулировал значения 5, 8 и 9, он может либо изменить второй элемент в этом массиве, либо создать новый массив, содержащий значения 5, 8 и 9, и отказаться от старого. Обратите внимание: если вы хотите сделать копию foo , необходимо в копии заменить arr ссылкой на новый массив, чтобы foo.arr оставался единственной ссылкой на этот массив в любой точке юниверса.

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

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

    Теоретически int[] должен быть хорошим простым четко определенным типом, но он имеет четыре очень разных значения. Напротив, ссылка на неизменяемый объект (например, String ) обычно имеет только одно значение. Большая часть «власти» непреложных объектов проистекает из этого факта.

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

    Неизменяемые средства не могут быть изменены, а mutable означает, что вы можете изменить.

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

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

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

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

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

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

    1. Создайте ярлык для txtfile и pas для вас или
    2. Возьмите копию для txtfile и pas copy для вас.

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

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

    Давайте будем гением компьютера.