Учитывая способ определения объекта, не должно быть никаких вопросов о «глубоком клонировании» по сравнению с «мелким клонированием». Если объект инкапсулирует идентичности вещей, клон объекта должен инкапсулировать идентичности тех же вещей. Если объект инкапсулирует значения изменяемых объектов, копия должна инкапсулировать отдельные изменяемые объекты, содержащие одинаковые значения.
К сожалению, ни .NET, ни Java не включают в систему типов, хранятся ли ссылки для инкапсуляции идентификатора, изменяемого значения, обоих или ни того, ни другого. Вместо этого они просто используют один ссылочный тип и показывают, что код, который владеет единственной копией ссылки или владеет единственной ссылкой на контейнер, содержащий единственную копию этой ссылки, может использовать эту ссылку для инкапсуляции либо значения, либо состояния. Такое мышление может быть приемлемым для отдельных объектов, но создает реальные проблемы, когда речь идет о таких вещах, как операции копирования и проверки на равенство.
Если у класса есть поле Foo
, которое инкапсулирует состояние List<Bar>
, которое должно инкапсулировать тождества объектов в нем, и может в будущем инкапсулировать тождества различных объектов, то клон Foo
должен хранить ссылку на новый список, который идентифицирует те же объекты. Если List<Bar>
используется для инкапсуляции изменяемых состояний объектов, то клон должен иметь ссылку на новый список, который идентифицирует новые объекты с таким же состоянием.
Если объекты включали отдельные методы «эквивалент» и «равно», с хэш-кодами для каждого, и если для каждого типа объекта кучи существовали ссылочные типы, которые были обозначены как инкапсулирующая идентичность, изменяемое состояние, оба или ни одного, тогда 99% проверки на равенство и методы клонирования могут быть обработаны автоматически. Два агрегата равны, если все компоненты, которые инкапсулируют идентичность или изменяемое состояние, эквивалентны (а не просто равны), и те, которые инкапсулируют ни то, ни другое, по крайней мере равны; два агрегата эквивалентны, только если все соответствующие компоненты являются и всегда будут эквивалентны [это часто подразумевает равенство ссылок, но не всегда]. Копирование агрегата требует создания отдельной копии каждой составляющей, которая инкапсулирует изменяемое состояние, копирования ссылки на каждую составляющую, которая инкапсулирует идентичность, и выполнения любого из вышеперечисленных для тех, которые не содержат ни одну из них; агрегат с компонентом, который инкапсулирует как изменяемое состояние, так и идентичность, не может быть просто клонирован.
Есть несколько хитрых случаев, когда такие правила клонирования, равенства и эквивалентности не будут обрабатываться должным образом, но если бы существовало соглашение, отличающее List<IdentityOfFoo>
от List<MutableStateOfFoo>
и поддерживающее оба «эквивалента» и тесты «равно», 99% объектов могут иметь автоматически сгенерированные клон, равно, эквивалентно, равноценность и равноценность и работать правильно.