Копирование объектов

Методика объектно-ориентированного программирования

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

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

Методы копирования

Цель дизайна большинства объектов — создать видимость того, что они сделаны из одного монолитного блока, хотя большинство из них таковыми не являются. Поскольку объекты состоят из нескольких различных частей, копирование становится нетривиальной задачей. Существует несколько стратегий для решения этой проблемы.

Рассмотрим объект A, содержащий поля x i (конкретнее, рассмотрим, является ли A строкой, а x i — массивом ее символов). Существуют различные стратегии создания копии A, называемые поверхностной копией и глубокой копией . Многие языки допускают обобщенное копирование с помощью одной или обеих стратегий, определяя либо одну операцию копирования , либо отдельные операции поверхностной копии и глубокой копии . [1] Обратите внимание, что еще более поверхностно использовать ссылку на существующий объект A, в этом случае нет нового объекта, только новая ссылка.

Терминология поверхностного копирования и глубокого копирования восходит к Smalltalk -80. [2] То же самое различие справедливо для сравнения объектов на равенство: в основном существует разница между идентичностью (один и тот же объект) и равенством (одинаковое значение), что соответствует поверхностному равенству и (1 уровень) глубокому равенству двух ссылок на объекты, но затем следует вопрос, означает ли равенство сравнение только полей рассматриваемого объекта или разыменование некоторых или всех полей и сравнение их значений по очереди (например, равны ли два связанных списка, если у них одинаковые узлы или если у них одинаковые значения?). [ необходимо разъяснение ]

Поверхностная копия

Одним из методов копирования объекта является поверхностное копирование . В этом случае создается новый объект B , а значения полей A копируются в B. [3] [4] [5] Это также известно как копирование поля за полем , [6] [7] [8] копирование поля для поля или копирование поля . [9] Если значение поля является ссылкой на объект (например, адрес памяти), оно копирует ссылку, следовательно, ссылаясь на тот же объект, что и A, а если значение поля является примитивным типом, оно копирует значение примитивного типа. В языках без примитивных типов (где все является объектом) все поля копии B являются ссылками на те же объекты, что и поля исходного A. Таким образом, объекты, на которые ссылаются, являются общими , поэтому если один из этих объектов изменяется (из A или B), изменение становится видимым в другом. Неглубокие копии просты и, как правило, дешевы, поскольку их обычно можно реализовать простым точным копированием битов.

Глубокая копия

Альтернативой является глубокое копирование, означающее, что поля разыменовываются: вместо ссылок на копируемые объекты создаются новые копии объектов для любых ссылаемых объектов, а ссылки на них помещаются в B. Последующие изменения содержимого любого из них остаются уникальными для A или B, поскольку содержимое не используется совместно.

Комбинация

В более сложных случаях некоторые поля в копии должны иметь общие значения с исходным объектом (как в поверхностной копии), что соответствует отношению «ассоциации»; а некоторые поля должны иметь копии (как в глубокой копии), что соответствует отношению «агрегации». В этих случаях обычно требуется пользовательская реализация копирования; эта проблема и ее решение относятся к Smalltalk-80. [10] В качестве альтернативы поля могут быть помечены как требующие поверхностной копии или глубокой копии, и операции копирования будут автоматически сгенерированы (аналогично для операций сравнения). [1] Однако это не реализовано в большинстве объектно-ориентированных языков, хотя частичная поддержка имеется в Eiffel. [1]

Выполнение

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

То, как решается проблема копирования, и какое понятие об объекте оно имеет, различается в разных языках.

Ленивая копия

Ленивое копирование — это реализация глубокого копирования. При первоначальном копировании объекта используется (быстрое) поверхностное копирование. Счетчик также используется для отслеживания того, сколько объектов совместно используют данные. Когда программа хочет изменить объект, она может определить, являются ли данные общими (проверив счетчик), и может выполнить глубокое копирование, если необходимо.

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

Ленивое копирование связано с копированием при записи .

На Яве

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

В отличие от C++, к объектам в Java всегда осуществляется косвенный доступ через ссылки . Объекты никогда не создаются неявно, а вместо этого всегда передаются или назначаются ссылочной переменной. (Методы в Java всегда передаются по значению , однако передается значение ссылочной переменной.) [11] Виртуальная машина Java управляет сборкой мусора , так что объекты очищаются после того, как они становятся недоступными. В Java нет автоматического способа скопировать любой заданный объект.

Копирование обычно выполняется методом clone() класса. Этот метод обычно, в свою очередь, вызывает метод clone() своего родительского класса для получения копии, а затем выполняет любые пользовательские процедуры копирования. В конце концов это доходит до метода clone() Object(самого верхнего класса), который создает новый экземпляр того же класса, что и объект, и копирует все поля в новый экземпляр («поверхностная копия»). Если используется этот метод, класс должен реализовать Cloneableинтерфейс маркера, иначе он выдаст «Clone Not Supported Exception». После получения копии из родительского класса собственный метод clone() класса может затем предоставить пользовательскую возможность клонирования, например глубокое копирование (т. е. дублирование некоторых структур, на которые ссылается объект) или присвоение новому экземпляру нового уникального идентификатора.

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

Недостатком является то, что часто невозможно получить доступ к методу clone() для абстрактного типа. Большинство интерфейсов и абстрактных классов в Java не определяют публичный метод clone(). Таким образом, часто единственный способ использовать метод clone() — это если класс объекта известен, что противоречит принципу абстракции использования максимально возможного общего типа. Например, если у вас есть ссылка List в Java, вы не можете вызвать clone() для этой ссылки, потому что List не определяет публичный метод clone(). Реализации List, такие как Array List и Linked List, как правило, имеют методы clone(), но это неудобно и плохая абстракция, чтобы переносить тип класса объекта.

Другой способ копирования объектов в Java — сериализация их через Serializableинтерфейс. Обычно это используется для целей сохранения и протокола проводов , но это создает копии объектов и, в отличие от клонирования, глубокая копия, которая изящно обрабатывает циклические графики объектов, легко доступна с минимальными усилиями со стороны программиста.

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

В Эйфелевой

Объекты времени выполнения в Eiffel доступны либо косвенно через ссылки , либо как расширенные объекты, поля которых встроены в объекты, которые их используют. То есть поля объекта хранятся либо внешне, либо внутренне .

Класс Eiffel ANYсодержит функции для поверхностного и глубокого копирования и клонирования объектов. Все классы Eiffel наследуются от ANY, поэтому эти функции доступны во всех классах и применимы как к ссылочным, так и к расширенным объектам.

Функция copyпроизводит поверхностное копирование поля за полем из одного объекта в другой. В этом случае новый объект не создается. Если yбыли скопированы в x, то те же объекты, на которые ссылался yдо применения copy, также будут ссылаться xпосле copyзавершения функции.

Для создания нового объекта, который является поверхностным дубликатом y, используется функция twin. В этом случае создается один новый объект с полями, идентичными полям источника.

Функция twinопирается на функцию copy, которая может быть переопределена в потомках ANY, если это необходимо. Результат twinимеет закрепленный тип like Current.

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

На других языках

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

В Objective-C методы copyи mutableCopyнаследуются всеми объектами и предназначены для выполнения копий; последний предназначен для создания изменяемого типа исходного объекта. Эти методы в свою очередь вызывают методы copyWithZoneи mutableCopyWithZone, соответственно, для выполнения копирования. Объект должен реализовать соответствующий copyWithZoneметод, чтобы быть копируемым.

В OCaml библиотечная функция Oo.copy выполняет поверхностное копирование объекта.

В Python модуль копирования библиотеки обеспечивает поверхностное и глубокое копирование объектов с помощью функций copy()и deepcopy()соответственно. [13] Программисты могут определять специальные методы __copy__()и __deepcopy__()в объекте для предоставления пользовательской реализации копирования.

В Ruby все объекты наследуют два метода для выполнения поверхностных копий, clone и dup. Два метода отличаются тем, что cloneкопируют испорченное состояние объекта, замороженное состояние и любые методы singletondup , которые он может иметь, тогда как копируют только испорченное состояние. Глубокие копии могут быть получены путем дампа и загрузки потока байтов объекта или сериализации YAML.[1] В качестве альтернативы вы можете использовать gem deep_dive для выполнения контролируемой глубокой копии ваших графов объектов.[2]

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

В VBA назначение переменных типа Objectявляется поверхностной копией, назначение для всех других типов (числовых типов, String, пользовательских типов, массивов) является глубокой копией. Таким образом, ключевое слово Setдля назначения сигнализирует о поверхностной копии, а (необязательное) ключевое слово Letсигнализирует о глубокой копии. Встроенного метода для глубоких копий объектов в VBA нет.

Смотрите также

Примечания

  1. ^ abc Грогоно и Саккинен 2000.
  2. ^ Голдберг и Робсон 1983, стр. 97–99. «Существует два способа создания копий объекта. Различие заключается в том, копируются ли значения переменных объекта. Если значения не копируются, то они являются общими ( shallowCopy); если значения копируются, то они не являются общими ( deepCopy).»
  3. ^ «Объяснение поверхностного и глубокого копирования в C++».
  4. ^ «Объяснение поверхностного и глубокого копирования в .NET».
  5. ^ "Общее объяснение поверхностного и глубокого копирования". Архивировано из оригинала 2016-03-04 . Получено 2013-04-10 .
  6. ^ Core Java: Основы, Том 1, стр. 295
  7. ^ Эффективная Java , Второе издание, стр. 54
  8. ^ «Что это за копирование по полям, выполняемое Object.clone()?», Stack Overflow
  9. ^ «Джош Блох о дизайне: беседа с автором эффективных программ на Java Джошем Блохом», Билл Веннерс, JavaWorld , 4 января 2002 г., стр. 13
  10. ^ Голдберг и Робсон 1983, стр. 97. «Реализация по умолчанию copyshallowCopy. В подклассах, в которых копирование должно приводить к особой комбинации общих и не общих переменных, метод, связанный с копией, обычно реализуется повторно, а не метод, связанный с shallowCopyили deepCopy».
  11. ^ "Передача информации методу или конструктору" . Получено 8 октября 2013 г.
  12. ^ Библиотека глубокого клонирования Java
  13. ^ Модуль копирования Python

Ссылки

  • Голдберг, Адель ; Робсон, Дэвид (1983). Smalltalk-80: Язык и его реализация . Пало-Альто, Калифорния: Xerox Palo Alto Research Center. ISBN 978-0-201-11371-6.
  • Грогоно, Питер; Саккинен, Маркку (12 мая 2000 г.). «Копирование и сравнение: проблемы и решения» (PDF) . В Elisa Bertino (ред.). Lecture Notes in Computer Science . ECOOP 2000 — Object-Oriented Programming. Vol. 1850. Springer Berlin Heidelberg. pp.  226– 250. doi :10.1007/3-540-45102-1_11 . Получено 23.06.2015 .
Получено с "https://en.wikipedia.org/w/index.php?title=Копирование_объектов&oldid=1261473247#Неглубокое_копирование"