Шаблон утилизации
Эта статья нуждается в дополнительных цитатах для проверки . ( февраль 2013 г. ) |
В объектно-ориентированном программировании шаблон удаления — это шаблон проектирования для управления ресурсами . В этом шаблоне ресурс удерживается объектом и освобождается путем вызова обычного метода , обычно называемого close
, dispose
, free
, release
в зависимости от языка – который освобождает любые ресурсы, которые удерживает объект. Многие языки программирования предлагают языковые конструкции , позволяющие избежать явного вызова метода удаления в распространенных ситуациях.
Шаблон удаления в основном используется в языках, среда выполнения которых имеет автоматическую сборку мусора (см. мотивацию ниже).
Мотивация
[ редактировать ]Обертывание ресурсов в объекты
[ редактировать ]Обертывание ресурсов в объекты — это объектно-ориентированная форма инкапсуляции , лежащая в основе шаблона удаления.
Ресурсы обычно представляются дескрипторами (абстрактными ссылками), точнее, целыми числами, которые используются для связи с внешней системой, предоставляющей ресурс. Например, файлы предоставляются операционной системой (в частности, файловой системой ), которая во многих системах представляет открытые файлы с файловым дескриптором (целым числом, представляющим файл).
Эти дескрипторы можно использовать напрямую, сохранив значение в переменной и передав его в качестве аргумента функциям, использующим этот ресурс. Однако часто бывает полезно абстрагироваться от самого дескриптора (например, если разные операционные системы представляют файлы по-разному) и хранить вместе с ним дополнительные вспомогательные данные, чтобы дескрипторы можно было хранить как поле в записи вместе с другими данные; если это непрозрачный тип данных , то это обеспечивает сокрытие информации , и пользователь абстрагируется от фактического представления.
Например, при вводе/выводе файлов C файлы представлены объектами класса FILE
тип (сбивчиво называемый « дескрипторы файлов »: это абстракция уровня языка), который хранит дескриптор (операционной системы) файла (например, файловый дескриптор ) вместе со вспомогательной информацией, такой как режим ввода-вывода (чтение, запись). ) и положение в потоке. Эти объекты создаются путем вызова fopen
(в объектно-ориентированных терминах — конструктор ), который получает ресурс и возвращает на него указатель; ресурс освобождается путем вызова fclose
по указателю на FILE
объект. [1] В коде:
FILE *f = fopen(filename, mode);
// Do something with f.
fclose(f);
Обратите внимание, что fclose
это функция с FILE *
параметр. В объектно-ориентированном программировании это метод экземпляра файлового объекта, как в Python:
f = open(filename)
# Do something with f.
f.close()
Это именно шаблон удаления, который отличается только синтаксисом и структурой кода. [а] от традиционного открытия и закрытия файлов. Другими ресурсами можно управлять точно так же: они могут быть получены в конструкторе или фабрике и освобождены явным close
или dispose
метод.
Оперативный выпуск
[ редактировать ]Фундаментальная проблема, которую призвано решить освобождение ресурсов, заключается в том, что ресурсы дороги (например, может существовать ограничение на количество открытых файлов), и поэтому их следует освобождать незамедлительно. Кроме того, иногда требуется некоторая работа по финализации, особенно для ввода-вывода, например, очистка буферов, чтобы гарантировать, что все данные действительно записаны.
Если ресурс неограничен или фактически неограничен и явная финализация не требуется, не важно освобождать его, и на самом деле недолговечные программы часто не освобождают ресурсы явно: из-за короткого времени выполнения они вряд ли исчерпают ресурсы. , и они полагаются на систему времени выполнения или операционную систему для выполнения любой финализации.
Однако в целом ресурсами необходимо управлять (особенно для долгоживущих программ, программ, использующих много ресурсов, или в целях безопасности, чтобы гарантировать запись данных). Явное удаление означает, что завершение и освобождение ресурсов происходит детерминировано и быстро: dispose
метод не завершится, пока они не будут выполнены.
Альтернативой требованию явного удаления является привязка управления ресурсами к сроку жизни объекта : ресурсы приобретаются во время создания объекта и освобождаются во время его уничтожения . Этот подход известен как идиома «Приобретение ресурсов — это инициализация » (RAII) и используется в языках с детерминированным управлением памятью (например, C++ ). В этом случае, в приведенном выше примере, ресурс приобретается при создании файлового объекта и когда область действия переменной f
завершается выход, файловый объект, который f
ссылается, уничтожается, и в результате этого ресурс освобождается.
RAII полагается на то, что время жизни объекта является детерминированным; однако при автоматическом управлении памятью время жизни объекта не беспокоит программиста: объекты уничтожаются в какой-то момент после того, как они больше не используются, а когда они абстрагируются. Действительно, время жизни часто не является детерминированным, хотя это может быть так, особенно если подсчет ссылок используется . Действительно, в некоторых случаях нет никакой гарантии, что объекты когда-либо будут финализированы: когда программа завершается, она может не финализировать объекты, а вместо этого просто позволить операционной системе освободить память; если требуется финализация (например, очистка буферов), может произойти потеря данных.
Таким образом, не связывая управление ресурсами со временем жизни объекта, шаблон утилизации позволяет ресурсы быстро освобождать , обеспечивая при этом гибкость реализации управления памятью. Платой за это является то, что ресурсами приходится управлять вручную, что может быть утомительно и чревато ошибками.
Ранний выход
[ редактировать ]Ключевая проблема с шаблоном удаления заключается в том, что если dispose
метод не вызывается, происходит утечка ресурса. Распространенной причиной этого является ранний выход из функции из-за раннего возврата или исключения.
Например:
def func(filename):
f = open(filename)
if a:
return x
f.close()
return y
Если функция возвращает результат при первом возврате, файл никогда не закрывается и происходит утечка ресурса.
def func(filename):
f = open(filename)
g(f) # Do something with f that may raise an exception.
f.close()
Если промежуточный код вызывает исключение, функция завершается раньше, и файл никогда не закрывается, поэтому происходит утечка ресурса.
С обеими этими задачами может справиться try...finally
конструкция, которая гарантирует, что предложениеfinally всегда выполняется при выходе:
def func(filename):
try:
f = open(filename)
# Do something.
finally:
f.close()
Более обобщенно:
Resource resource = getResource();
try {
// Resource has been acquired; perform actions with the resource.
...
} finally {
// Release resource, even if an exception was thrown.
resource.dispose();
}
The try...finally
конструкция необходима для обеспечения надлежащей безопасности исключений , поскольку finally
позволяет выполнить логику очистки независимо от того, выдано исключение или нет в try
блокировать.
Одним из недостатков этого подхода является то, что он требует от программиста явного добавления кода очистки в файл. finally
блокировать. Это приводит к раздуванию размера кода, а несоблюдение этого требования приведет к утечке ресурсов в программе.
Языковые конструкции
[ редактировать ]Чтобы сделать безопасное использование шаблона расположения менее многословным, в некоторых языках есть встроенная поддержка ресурсов, которые хранятся и освобождаются в одном и том же блоке кода .
Язык C# имеет using
заявление [2] который автоматически вызывает Dispose
метод объекта, который реализует IDisposable
интерфейс :
using (Resource resource = GetResource())
{
// Perform actions with the resource.
...
}
что равно:
Resource resource = GetResource()
try
{
// Perform actions with the resource.
...
}
finally
{
// Resource might not been acquired, or already freed
if (resource != null)
((IDisposable)resource).Dispose();
}
Аналогично, язык Python имеет with
оператор, который можно использовать для аналогичного эффекта с объектом диспетчера контекста . Протокол контекстного менеджера требует реализации __enter__
и __exit__
методы, которые автоматически вызываются with
конструкции оператора, чтобы предотвратить дублирование кода, которое в противном случае могло бы произойти с try
/ finally
шаблон. [3]
with resource_context_manager() as resource:
# Perform actions with the resource.
...
# Perform other actions where the resource is guaranteed to be deallocated.
...
В языке Java появился новый синтаксис, названный try
-with-resources в Java версии 7. [4] Его можно использовать для объектов, реализующих интерфейс AutoCloseable (который определяет метод close()):
try (OutputStream x = new OutputStream(...)) {
// Do something with x
} catch (IOException ex) {
// Handle exception
// The resource x is automatically closed
} // try
Проблемы
[ редактировать ]Помимо ключевой проблемы правильного управления ресурсами при наличии возвратов и исключений и управления ресурсами на основе кучи (удаление объектов в области, отличной от той, в которой они были созданы), существует множество других сложностей, связанных с шаблоном удаления. в значительной степени позволяет избежать этих проблем RAII . Однако при обычном простом использовании этих сложностей не возникает: приобретите один ресурс, сделайте с ним что-нибудь, автоматически освободите его.
Фундаментальная проблема заключается в том, что наличие ресурса больше не является инвариантом класса (ресурс удерживается с момента создания объекта до его удаления, но в этот момент объект все еще активен), поэтому ресурс может быть недоступен, когда объект пытается используйте его, например, пытаясь прочитать закрытый файл. Это означает, что все методы объекта, использующие этот ресурс, потенциально терпят неудачу, обычно из-за возврата ошибки или возникновения исключения. На практике это незначительно, поскольку использование ресурсов обычно может завершиться неудачей и по другим причинам (например, попытка прочитать конец файла), поэтому эти методы уже могут потерпеть неудачу, а отсутствие ресурса просто добавляет еще один возможный сбой. . Стандартный способ реализовать это — добавить к объекту логическое поле, называемое disposed
, для которого установлено значение true dispose
и проверяется защитным предложением для всех методов (использующих ресурс), вызывая исключение (например, ObjectDisposedException
в .NET), если объект был удален. [5]
Далее можно позвонить dispose
на объекте более одного раза. Хотя это может указывать на ошибку программирования (каждый объект, содержащий ресурс, должен быть удален ровно один раз), это проще, надежнее и, следовательно, обычно предпочтительнее для dispose
быть идемпотентным (что означает «вызов несколько раз — то же самое, что вызов один раз»). [5] Это легко реализовать, используя то же логическое значение disposed
поле и проверяем его в защитном предложении в начале dispose
, в этом случае возвращается немедленно, а не вызывает исключение. [5] Java различает одноразовые типы (те, которые реализуют AutoCloseable ) от одноразовых типов, где Dispose является идемпотентным (подтип Closeable ).
Утилизация при наличии наследования и композиции объектов, содержащих ресурсы, имеет аналогичные проблемы с уничтожением/финализацией (через деструкторы или финализаторы). Кроме того, поскольку шаблон удаления обычно не имеет для этого языковой поддержки, шаблонный код необходим . Во-первых, если производный класс переопределяет dispose
в базовом классе, переопределяющий метод в производном классе обычно должен вызывать метод dispose
в базовом классе, чтобы правильно освободить ресурсы, хранящиеся в базе. Во-вторых, если объект имеет отношение «имеет» с другим объектом, который содержит ресурс (т. е. если объект косвенно использует ресурс через другой объект, который напрямую использует ресурс), должен ли косвенно использующий объект быть одноразовым? Это соответствует тому, являются ли отношения владением ( композиция объекта ) или просмотром ( агрегация объектов ) или даже просто общением ( ассоциация ), и оба соглашения обнаружены (косвенный пользователь несет ответственность за ресурс или не несет ответственности). Если за ресурс отвечает косвенное использование, он должен быть одноразовым и удалять принадлежащие ему объекты при его удалении (аналогично уничтожению или завершению принадлежащих объектов).
Композиция (владение) обеспечивает инкапсуляцию (следить нужно только за используемым объектом), но за счет значительной сложности, когда между объектами существуют дальнейшие связи, тогда как агрегация (просмотр) значительно упрощается за счет отсутствия инкапсуляции. В .NET соглашение заключается в том, чтобы ответственность возлагалась только на непосредственного пользователя ресурсов: «Вам следует реализовывать IDisposable только в том случае, если ваш тип напрямую использует неуправляемые ресурсы». [6] см . в разделе «Управление ресурсами» Подробности и дополнительные примеры .
См. также
[ редактировать ]Примечания
[ редактировать ]- ^ В программировании на основе классов методы определяются в классе с использованием неявного
this
илиself
параметр, а не как функции, принимающие явный параметр.
Ссылки
[ редактировать ]- ^ Единая спецификация UNIX , версия 4 от Open Group. – Справочник базовых определений,
- ^ Microsoft MSDN: использование оператора (ссылка на C#)
- ^ Гвидо ван Россум , Ник Коглан (13 июня 2011 г.). «PEP 343: Заявление «с»» . Фонд программного обеспечения Python.
- ^ Учебное пособие по Oracle Java: оператор try-with-resources
- ^ Перейти обратно: а б с «Утилизация шаблона» .
- ^ «Одноразовый интерфейс» . Проверено 3 апреля 2016 г.
Дальнейшее чтение
[ редактировать ]- Сеть разработчиков Microsoft: шаблон удаления