Я не могу получить то, что сценарии, где нам нужен непреложный класс.
Вы когда-нибудь сталкивались с таким требованием? или вы можете дать нам какой-либо реальный пример, где мы должны использовать этот шаблон.
Почему нам нужен непреложный класс?
Ответ 1
Другие ответы, похоже, были сфокусированы на объяснении того, почему неизменяемость хороша. Это очень хорошо, и я использую его, когда это возможно. Однако это не ваш вопрос. Я возьму ваш вопрос, чтобы попытаться убедиться, что вы получаете ответы и примеры, которые вам нужны.
Я не могу получить то, что является сценариями, где нам нужен неизменный класс.
"Need" - относительный термин здесь. Неизменяемые классы - это шаблон дизайна, который, как и любая парадигма/шаблон/инструмент, позволяет упростить создание программного обеспечения. Аналогично, большое количество кода было написано до того, как появилась парадигма OO, но посчитайте меня среди программистов, которые "нуждаются" в OO. Неизменяемые классы, такие как OO, не нужны строго, но я буду действовать так, как они мне нужны.
Вы когда-нибудь сталкивались с таким требованием?
Если вы не рассматриваете объекты в проблемном домене с правильной перспективой, вы можете не видеть требования к неизменяемому объекту. Было бы легко подумать, что проблемная область не требует каких-либо неизменяемых классов, если вы не знакомы, когда их использовать выгодно.
Я часто использую неизменяемые классы, где я думаю о данном объекте в моей проблемной области как значение или фиксированный экземпляр. Это понятие иногда зависит от перспективы или точки зрения, но в идеале легко будет переключиться в правильную перспективу, чтобы идентифицировать хорошие объекты-кандидата.
Вы можете лучше понять, где непреложные объекты действительно полезны (если не строго необходимы), убедившись, что вы читаете в разных книгах/онлайн-статьях, чтобы развить здравый смысл, как думать о непреложных классах. Одна хорошая статья, чтобы вы начали: Теория и практика Java: мутировать или не мутировать?
Я попытаюсь привести несколько примеров ниже о том, как можно увидеть объекты в разных перспективах (изменяемые и неизменные), чтобы уточнить, что я имею в виду под перспективой.
... не могли бы вы дать нам какой-либо реальный пример, где мы должны использовать этот шаблон.
Поскольку вы попросили реальных примеров, я дам вам некоторые, но сначала начнем с некоторых классических примеров.
Классические объекты ценности
Строки и целые числа и часто считаются значениями. Поэтому неудивительно, что класс String и класс оболочки Integer (а также другие классы-оболочки) неизменяемы в Java. Цвет обычно считается значением, поэтому неизменный класс Color.
Контрпример
В отличие от этого, автомобиль обычно не рассматривается как объект ценности. Моделирование автомобиля обычно означает создание класса, который меняет состояние (одометр, скорость, уровень топлива и т.д.). Тем не менее, есть некоторые домены, где автомобиль может быть объектом ценности. Например, автомобиль (или, в частности, модель автомобиля) можно рассматривать как объект ценности в приложении для поиска правильного моторного масла для данного автомобиля.
Игральные карты
Вы когда-нибудь писали программу игровых карт? Я сделал. Я мог бы представить игральную карту как изменчивый объект с изменчивым костюмом и рангом. Рука ничьей-покера может быть 5 фиксированных экземпляров, где замена пятой карты в моей руке означала бы мутацию пятого экземпляра игровой карты в новую карту, изменив ее костюм и ранги ivars.
Однако я склонен думать, что игральная карта является непреложным объектом, который имеет фиксированный неизменный костюм и ранг после создания. Моя рука в покере будет 5 экземпляров, и замена карты в моей руке приведет к отбрасыванию одного из этих экземпляров и добавлению нового случайного экземпляра в мою руку.
Проекция карты
Последний пример - когда я работал над некоторым кодом карты, где карта могла отображаться в различных проекциях. В исходном коде карта использовала фиксированный, но мутируемый экземпляр проекции (например, измененную игровую карту выше). Изменение проекции карты означало мутацию проекции экземпляра карты проекции (тип проекции, центральная точка, масштабирование и т.д.).
Тем не менее, я чувствовал, что дизайн был проще, если я думал о проекции как неизменяемой ценности или фиксированного экземпляра. Изменение проекции карты означало наличие ссылки на карту другого экземпляра проекции, а не мутирования фиксированного экземпляра проекции карты. Это также упростило захват названных проекций, таких как MERCATOR_WORLD_VIEW
.
Ответ 2
Необязательные классы, как правило, значительно упрощают проектирование, внедрение и использование. Примером является String: реализация java.lang.String
значительно проще, чем реализация std::string
в С++, в основном из-за ее неизменности.
Одна особая область, в которой неизменяемость особенно важна, - это concurrency: неизменяемые объекты можно безопасно распределять между несколькими потоками, тогда как изменяемые объекты должны быть потокобезопасными посредством тщательного проектирования и реализации - обычно это далеко не тривиальная задача.
Обновление: Эффективное Java 2nd Edition подробно решает эту проблему - см. пункт 15: Минимизируйте изменчивость.
См. также эти связанные сообщения:
Ответ 3
Эффективная Java от Джошуа Блоха описывает несколько причин для написания неизменяемых классов:- Простота - каждый класс находится только в одном состоянии
- Thread Safe - поскольку состояние не может быть изменено, синхронизация не требуется
- Запись в неизменяемом стиле может привести к созданию более надежного кода. Представьте, что Струны не были неизменными; Любые методы getter, которые возвратили String, потребовали бы, чтобы реализация создала защитную копию до того, как была возвращена String, иначе клиент может случайно или злонамеренно нарушить это состояние объекта.
В целом, хорошая практика - сделать объект неизменным, если в результате не возникнут серьезные проблемы с производительностью. В таких обстоятельствах изменяемые объекты-строители могут использоваться для создания неизменяемых объектов, например. StringBuilder
Ответ 4
Хашмапы - классический пример. Необходимо, чтобы ключ к карте был неизменным. Если ключ не является неизменным, и вы изменяете значение на ключе, так что hashCode() приведет к новому значению, теперь карта сломана (теперь ключ находится в неправильном месте в хэш-таблице).
Ответ 5
Java - это практически одна и все ссылки. Иногда экземпляр ссылается несколько раз. Если вы измените такой экземпляр, он будет отражен во всех его ссылках. Иногда вы просто не хотите, чтобы это улучшало надежность и безопасность потоков. Тогда неизменяемый класс полезен, поэтому необходимо создать экземпляр new и переназначить его в текущую ссылку. Таким образом, исходный экземпляр других ссылок остается нетронутым.
Представьте себе, как выглядит Java, если String
изменен.
Ответ 6
Нам не нужны непреложные классы, но они могут, конечно, облегчить некоторые задачи программирования, особенно если задействованы несколько потоков. Вам не нужно выполнять блокировку для доступа к неизменяемому объекту, и любые факты, которые вы уже установили в отношении такого объекта, по-прежнему будут иметь значение в будущем.
Ответ 7
Существуют разные причины неизменности:
- Безопасность потоков: неизменяемые объекты не могут быть изменены и не могут измениться его внутреннее состояние, поэтому нет необходимости его синхронизировать.
- Это также гарантирует, что все, что я отправляю через (через сеть), должно быть в том же состоянии, что и ранее отправлено. Это означает, что никто (подслушивающее устройство) не может прийти и добавить случайные данные в мой неизменный набор.
- Это также проще разработать. Вы гарантируете, что никакие подклассы не будут существовать, если объект является неизменным. Например. a
String
.
Итак, если вы хотите отправить данные через сетевую услугу и хотите получить гарантию, что ваш результат будет точно таким же, как и вы отправили, установите его как неизменяемый.
Ответ 8
Возьмем крайний случай: целочисленные константы. Если я напишу выражение типа "x = x + 1", я хочу быть на 100% доверенным, что число "1" не станет каким-то образом 2, независимо от того, что происходит в другом месте программы.
Теперь все в порядке, целые константы не являются классом, но концепция одинаков. Предположим, что я пишу:
String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);
Выглядит достаточно просто. Но если строки не были неизменными, тогда мне пришлось бы учитывать возможность того, что getCustomerName может изменить customerId, так что, когда я вызываю getCustomerBalance, я получаю баланс для другого клиента. Теперь вы можете сказать: "Почему в мире кто-то, пишущий функцию getCustomerName, изменит идентификатор? Это не имеет смысла". Но это именно то, где вы могли попасть в беду. Человек, написавший вышеуказанный код, может считать это просто очевидным, что функции не изменят параметр. Затем приходит кто-то, кто должен изменить другое использование этой функции, чтобы обрабатывать случай, когда клиент имеет несколько учетных записей под тем же именем. И он говорит: "О, вот эта удобная функция имени получателя, которая уже ищет имя. Я просто сделаю это, чтобы автоматически изменить идентификатор на следующую учетную запись с тем же именем и поместить ее в цикл..." И то ваша программа начинает таинственно не работать. Будет ли это плохой стиль кодирования? Вероятно. Но это точно проблема в случаях, когда побочный эффект НЕ очевиден.
Неизменяемость просто означает, что определенный класс объектов является константой, и мы можем рассматривать их как константы.
(Конечно, пользователь может назначить переменную "постоянный объект" для переменной. Кто-то может написать Строка s = "привет"; а затем написать с = "до свидания"; Если я не сделаю переменную окончательной, я не могу быть уверен, что она не будет изменена в моем собственном блоке кода. Как и целочисленные константы, заверить меня, что "1" всегда совпадает с номером, но не "x = 1" никогда не будет изменено, написав "x = 2". Но я могу быть доверенным, что, если у меня есть дескриптор неизменяемого объекта, то никакая функция, которую я передаю, не может изменить его на мне, или что если я сделаю две копии этого, то изменение переменной, содержащей одну копию, не будет измените другое. Etc.
Ответ 9
Я собираюсь атаковать это с другой точки зрения. Я нахожу неизменные объекты, которые облегчают жизнь при чтении кода.
Если у меня есть изменяемый объект, я никогда не сомневаюсь в его ценности, если он когда-либо использовался вне моей непосредственной области. Скажем, я создаю MyMutableObject
в локальных переменных метода, заполняю его значениями, а затем передаю его еще пяти методам. ЛЮБОЙ ОДИН из этих методов может изменить состояние моего объекта, так что одна из двух вещей должна произойти:
- Мне нужно отслеживать тела из пяти дополнительных методов, думая о моей логике кода.
- Мне нужно сделать пять расточительных защитных копий моего объекта, чтобы гарантировать, что нужные значения передаются каждому методу.
Вначале сложно рассуждать о моем коде. Во-вторых, мой код сосать в производительности - я в основном имитирую неизменяемый объект с семантикой copy-on-write в любом случае, но все время делаю это независимо от того, действительно ли вызываемые методы изменяют мое состояние объекта.
Если я вместо этого использую MyImmutableObject
, я могу быть уверен, что то, что я установил, - это то, что значения будут для жизни моего метода. Там нет "жуткий действия на расстоянии", который изменит его из-под меня, и мне не нужно делать защитные копии моего объекта, прежде чем ссылаться на пять других методов. Если другие методы хотят изменить вещи для своих целей , они должны сделать копию – но они делают это только в том случае, если им действительно нужно сделать копию (в отличие от моего выполнения перед каждым вызовом внешних методов). Я оставляю себе умственные ресурсы для отслеживания методов, которые могут быть даже не в моем текущем исходном файле, и я избавляю систему от накладных расходов, бесконечно делая ненужные защитные копии на всякий случай.
(Если я выхожу за пределы мира Java и, скажем, в мир С++, среди прочих, я могу стать еще более сложным. Я могу сделать объекты такими, как будто они изменяемы, но за кулисами делают их прозрачными клон при любых изменениях состояния - это копирование на запись, а никто не мудрее.
Ответ 10
Использование ключевого слова final не обязательно делает что-то неизменяемым:
public class Scratchpad {
public static void main(String[] args) throws Exception {
SomeData sd = new SomeData("foo");
System.out.println(sd.data); //prints "foo"
voodoo(sd, "data", "bar");
System.out.println(sd.data); //prints "bar"
}
private static void voodoo(Object obj, String fieldName, Object value) throws Exception {
Field f = SomeData.class.getDeclaredField("data");
f.setAccessible(true);
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.set(obj, "bar");
}
}
class SomeData {
final String data;
SomeData(String data) {
this.data = data;
}
}
Просто пример, демонстрирующий, что ключевое слово "final" существует, чтобы предотвратить ошибку программиста и не намного больше. В то время как переназначение значения, не имеющего ключевого слова final, может легко произойти случайно, идя на эту длину, чтобы изменить значение, нужно было бы сделать намеренно. Это там для документации и для предотвращения ошибки программиста.
Ответ 11
Непрерывные структуры данных также могут помочь при кодировании рекурсивных алгоритмов. Например, скажите, что вы пытаетесь решить проблему 3SAT. Один из способов - сделать следующее:
- Выберите недопустимую переменную.
- Дайте ему значение TRUE. Упростите экземпляр, выведя предложения, которые теперь выполняются, и повторите, чтобы решить более простой экземпляр.
- Если рекурсия в случае с TRUE не удалась, вместо этого назначьте эту переменную FALSE. Упростите этот новый экземпляр и повторите его, чтобы решить проблему.
Если у вас есть изменяемая структура для представления проблемы, тогда, когда вы упрощаете экземпляр в ветке TRUE, вам нужно либо:
- Следите за всеми изменениями, которые вы совершаете, и отменяете их все, как только осознаете, что проблема не может быть решена. У этого есть большие накладные расходы, потому что ваша рекурсия может идти довольно глубоко, и это сложно сделать для кода.
- Сделайте копию экземпляра, а затем измените копию. Это будет медленным, потому что, если ваша рекурсия составляет несколько десятков уровней, вам нужно будет сделать много копий экземпляра.
Однако, если вы правильно его кодируете, вы можете иметь неизменяемую структуру, где любая операция возвращает обновленную (но все еще неизменяемую) версию проблемы (аналогично String.replace
- она не заменяет строку, просто дает вам новый). Наивный способ реализовать это состоит в том, чтобы "неизменяемая" структура просто копировала и создавала новую для любых модификаций, сводя ее ко второму решению, имея сменную, со всеми этими накладными расходами, но вы можете сделать это в более эффективный способ.
Ответ 12
Одной из причин "необходимости" для неизменяемых классов является комбинация передачи всего по ссылке и отсутствие поддержки просмотра только для чтения объекта (т.е. С++ const
).
Рассмотрим простой случай класса, имеющего поддержку шаблона наблюдателя:
class Person {
public string getName() { ... }
public void registerForNameChange(NameChangedObserver o) { ... }
}
Если string
не были неизменяемы, было бы невозможно, чтобы класс Person
правильно реализовал registerForNameChange()
, потому что кто-то мог написать следующее, эффективно изменяя имя человека, не вызывая никакого уведомления.
void foo(Person p) {
p.getName().prepend("Mr. ");
}
В С++ getName()
возврат const std::string&
приводит к возврату по ссылке и предотвращению доступа к мутаторам, что означает, что неизменяемые классы не нужны в этом контексте.
Ответ 13
Они также дают нам гарантию. Гарантия неизменности означает, что мы можем расширять их и создавать новые паттеры для эффективности, которые в противном случае невозможны.
Ответ 14
Одна особенность неизменяемых классов, которые еще не были вызваны: сохранение ссылки на объект с неизменяемым классом является эффективным средством хранения всего содержащегося в нем состояния. Предположим, что у меня есть изменяемый объект, который использует глубоко неизменный объект для хранения информации о состоянии на 50 КБ. Предположим, далее, что я хочу в 25 случаях сделать "копию" моего исходного (изменяемого) объекта (например, для "отменить" ); состояние может меняться между операциями копирования, но обычно этого не происходит. Создание "копии" изменяемого объекта просто потребовало бы копирования ссылки на его неизменяемое состояние, поэтому 20 экземпляров просто составят 20 ссылок. В отличие от этого, если бы государство было размещено на 50 тыс. Измененных объектов, каждая из 25 операций копирования должна была создать свою собственную копию данных на 50 тыс. для хранения всех 25 экземпляров потребовалось бы хранить большую сумму дублированных данных. Несмотря на то, что первая операция копирования создаст копию данных, которые никогда не будут меняться, а остальные 24 операции могут теоретически просто ссылаться на это, в большинстве реализаций не было бы способа, чтобы второй объект запрашивал копию информацию о том, что неизменяемая копия уже существует (*).
(*) Один шаблон, который иногда может быть полезным, заключается в том, что для изменяемых объектов есть два поля для хранения своего состояния - одно в изменяемой форме и одно в неизменяемой форме. Объекты могут быть скопированы как изменчивые или неизменные и начнут жизнь с одного или другого набора ссылок. Как только объект хочет изменить свое состояние, он копирует неизменяемую ссылку на изменяемую (если она еще не была сделана) и делает недействительной неизменяемую. Когда объект копируется как неизменяемый, если его неизменяемая ссылка не задана, будет создана неизменяемая копия, и на нее будет ссылаться неизменная ссылка. Этот подход потребует еще нескольких операций копирования, чем "полноценная копия при записи" (например, запрос на копирование объекта, который был мутирован, поскольку для последней копии требуется операция копирования, даже если исходный объект больше не мутируется), но это позволяет избежать сложности потоков, которые повлечет за собой FFCOW.
Ответ 15
из эффективной Java; Неизменяемый класс - это просто класс, экземпляры которого не могут быть изменены. Все информация, содержащаяся в каждом экземпляре, предоставляется, когда она создана, и фиксированный для времени жизни объекта. Библиотеки платформы Java содержат много неизменяемые классы, включая String, примитивные классы в штучной упаковке и BigInte- ger и BigDecimal. Для этого есть много веских причин: неизменные классы легче разрабатывать, реализовывать и использовать, чем изменяемые классы. Они менее подвержены к ошибке и более безопасны.
Ответ 16
По неизменности вы можете быть уверены, что поведение не изменится, и вы получите дополнительное преимущество в выполнении дополнительных операций:
-
Вы можете использовать несколько ядро /обработка (параллельная обработка) с помощью (поскольку последовательность больше не имеет значения.)
-
Можно выполнить кеширование для дорогостоящей работы (так как вы уверены в том же | результат).
-
Можно легко отлаживать (поскольку история запуска не будет проблемой) больше)