Зачем нам нужен неизменный класс? - PullRequest
70 голосов
/ 22 сентября 2010

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

Ответы [ 19 ]

71 голосов
/ 22 сентября 2010

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

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

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

Вы когда-нибудь сталкивались с таким требованием?

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

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

Вы можете лучше понять, где неизменные объекты действительно полезны (если в этом нет особой необходимости), убедившись, что вы читаете различные книги / онлайн-статьи, чтобы развить хорошее понимание того, как думать о неизменные классы. Хорошая статья для начала - Теория и практика Java: мутировать или не мутировать?

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

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

Поскольку вы попросили реальные примеры, я дам вам несколько, но сначала давайте начнем с некоторых классических примеров.

Классические объекты значения

Строки и целые числа и часто рассматриваются как значения. Поэтому неудивительно, что класс String и класс-обертка Integer (а также другие классы-обертки) неизменны в Java. Цвет обычно рассматривается как значение, то есть неизменный класс Color.

Контрпример

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

Игральные карты

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

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

Карта проекции

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

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

42 голосов
/ 22 сентября 2010

Неизменяемые классы, как правило, намного проще для разработки, реализации и правильного использования . Примером является String: реализация java.lang.String значительно проще, чем реализация std::string в C ++, в основном из-за его неизменности.

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

Обновление: Effective Java 2nd Edition подробно решает эту проблему - см. Элемент 15: Минимизируйте изменчивость .

Смотрите также эти похожие сообщения:

38 голосов
/ 22 сентября 2010

Эффективная Java от Джошуа Блоха описывает несколько причин написания неизменных классов:

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

Как правило, рекомендуется сделать объект неизменным, если в результате не возникнут серьезные проблемы с производительностью. В таких обстоятельствах изменяемые объекты-строители могут использоваться для создания неизменяемых объектов, например, StringBuilder

13 голосов
/ 22 сентября 2010

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

8 голосов
/ 22 сентября 2010

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

Представьте, как будет выглядеть Java, если бы String был изменяемым.

6 голосов
/ 22 сентября 2010

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

5 голосов
/ 22 сентября 2010

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

Если у меня есть изменяемый объект, я никогда не уверен, каково его значение, если он когда-либо используется вне моей непосредственной области видимости.Допустим, я создаю MyMutableObject в локальных переменных метода, заполняю его значениями, а затем передаю его пяти другим методам.ЛЮБОЙ из этих методов может изменить состояние моего объекта, поэтому должна произойти одна из двух вещей:

  1. Я должен отслеживать тела пяти дополнительных методов, думая о логике моего кода.
  2. Мне нужно сделать пять бесполезных защитных копий моего объекта, чтобы обеспечить передачу правильных значений каждому методу.

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

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

(Если я выхожу наружуиз мира Java и, скажем, в мир C ++, среди прочего, я могу стать еще хитрее. Я могу заставить объекты выглядеть так, как будто они изменчивы, но за кулисами сделать их прозрачно клонируемыми при любых изменениях состояния -это копирование при записи - никто не может быть мудрее.)

5 голосов
/ 22 сентября 2010

Давайте возьмем крайний случай: целочисленные константы.Если я напишу утверждение типа «x = x + 1», я хочу быть на 100% доверенным лицом, что число «1» не станет каким-то образом 2, независимо от того, что происходит где-либо еще в программе.

Теперь все в порядкецелочисленные константы не являются классом, но концепция та же самая.Предположим, я пишу:

String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);

Выглядит достаточно просто.Но если бы строки не были неизменяемыми, мне пришлось бы учитывать возможность того, что getCustomerName может изменить customerId, поэтому при вызове getCustomerBalance я получаю баланс для другого клиента.Теперь вы можете сказать: «Почему в мире кто-то, пишущий функцию getCustomerName, может изменить идентификатор? Это не имеет смысла».Но это именно то, где вы можете попасть в беду.Человек, пишущий приведенный выше код, может считать очевидным, что функции не будут изменять параметр.Затем приходит кто-то, кто должен изменить другое использование этой функции для обработки случая, когда у клиента есть несколько учетных записей под одним и тем же именем.И он говорит: «О, вот эта удобная функция имени getCustomer, которая уже ищет имя. Я просто сделаю это, чтобы автоматически сменить идентификатор следующей учетной записи с тем же именем и поместил ее в цикл ...» Итогда ваша программа загадочным образом не работает.Это был бы плохой стиль кодирования?Наверное.Но это именно проблема в случаях, когда побочный эффект НЕ очевиден.

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

(Конечнопользователь может назначить другой «постоянный объект» для переменной. Кто-то может написать String s = "hello", а затем написать s = "goodbye"; пока я не сделаю переменную final, я не могу быть уверен, что это не такменяются в моем собственном блоке кода. Также как целочисленные константы уверяют меня, что «1» всегда одно и то же число, но не то, что «x = 1» никогда не изменится, написав «x = 2». Но я могу быть доверенным лицомчто если у меня есть дескриптор неизменного объекта, что никакая функция, которой я передаю его, не может изменить его на меня, или что, если я сделаю две копии, изменение в переменной, содержащей одну копию, не изменит другую..

5 голосов
/ 22 сентября 2010

Существуют различные причины неизменности:

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

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

2 голосов
/ 21 августа 2018

Мои 2 цента для будущих посетителей:


2 сценария, в которых неизменные объекты являются хорошим выбором:

В многопоточности

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


В качестве ключа для коллекций на основе хеша

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...