Шаблон удаления

редактировать

В объектно-ориентированном программировании шаблон удаления является дизайном шаблон для управления ресурсами. В этом шаблоне ресурс удерживается объектом и освобождается путем вызова обычного метода - обычно называемого close, dispose, free, releaseв зависимости от языка, который освобождает любые ресурсы, которые объект удерживает. Многие языки предлагают языковые конструкции, чтобы избежать явного вызова метода dispose в общих ситуациях.

Шаблон удаления в основном используется в языках, среда выполнения которых имеет автоматическую сборку мусора (см. Мотивацию ниже).

Содержание
  • 1 Мотивация
    • 1.1 Обертывание ресурсов в объектах
    • 1.2 Быстрый выпуск
  • 2 Ранний выход
  • 3 Языковые конструкции
  • 4 Проблемы
  • 5 См. Также
  • 6 Примечания
  • 7 Ссылки
  • 8 Дополнительная литература
Мотивация

Обертывание ресурсов в объектах

Обертывание ресурсов в объектах является объектно-ориентированной формой инкапсуляции, и лежит в основе шаблона удаления.

Ресурсы обычно представлены дескрипторами (абстрактными ссылками), обычно обычно целыми числами, которые используются для связи с внешней системой, предоставляющей ресурс. Например, файлы предоставляются операционной системой (в частности, файловой системой ), которая во многих системах представляет открытые файлы с файловым дескриптором (целое число, представляющее файл).

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

Например, во вводе / выводе файла C файлы представлены объектами типа FILE(ошибочно называется «файловые дескрипторы » : это абстракция на уровне языка), в которой хранится дескриптор (операционной системы) файла (например, дескриптор файла ) вместе со вспомогательной информацией, такой как режим ввода-вывода (чтение, запись) и положение в потоке. Эти объекты создаются путем вызова fopen (в объектно-ориентированных терминах, конструктор ), который получает ресурс и возвращает указатель на него; ресурс освобождается вызовом fclose для указателя на объект FILE. В коде:

ФАЙЛ * f = fopen (имя файла, режим); // Что-нибудь делаем с f. fclose (f);

Обратите внимание, что fclose- это функция с параметром FILE *. В объектно-ориентированном программировании это метод экземпляра для файлового объекта, как в Python:

f = open (filename) # Сделайте что-нибудь с f. f.close ()

Это как раз шаблон удаления, который отличается только синтаксисом и структурой кода от традиционного открытия и закрытия файлов. Другими ресурсами можно управлять точно так же: они могут быть получены в конструкторе или фабрике и освобождены явным методом closeили dispose.

Быстрый выпуск

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

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

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

Альтернативой требованию явного удаления является привязка управления ресурсами к времени жизни объекта : ресурсы приобретаются во время создания объекта и высвобождаются во время уничтожения объекта. Этот подход известен как идиома Resource Acquisition Is Initialization (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) # Сделайте что-нибудь с f, что может вызвать исключение. f.close ()

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

И то и другое может быть обработано конструкцией try... finally, которая гарантирует, что предложение finally всегда выполняется при выходе:

def func (filename): try: f = open (filename) # Сделайте что-нибудь. наконец: f.close ()

В более общем смысле:

Resource resource = getResource (); try {// Ресурс получен; выполнять действия с ресурсом....} finally {// Освобождаем ресурс, даже если было сгенерировано исключение. resource.dispose (); }

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

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

Языковые конструкции

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

В языке C # есть оператор using, который автоматически вызывает метод Disposeдля объекта, реализующего IDisposableinterface :

using (Resource resource = GetResource ()) {// Выполняем действия с ресурсом....}

который равен:

Resource resource = GetResource () try {// Выполняем действия с ресурсом....} finally {// Ресурс может не быть получен или уже освобожден if (resource! = null) ((IDisposable) resource).Dispose (); }

Точно так же в языке Python есть оператор с, который можно использовать с аналогичным эффектом с объектом диспетчера контекста. Протокол диспетчера контекста требует реализации методов __enter__и __exit__, которые автоматически вызываются конструкцией оператора with, чтобы предотвратить дублирование кода, которое в противном случае могло бы произойти с попробуйте/ finallypattern.

с resource_context_manager () в качестве ресурса: # Выполните действия с ресурсом.... # Выполните другие действия, при которых ресурс гарантированно будет освобожден....

Язык Java представил новый синтаксис под названием try-with-resources в Java версии 7. Он может использоваться с объектами, реализующими интерфейс AutoCloseable (который определяет method close ()):

try (OutputStream x = new OutputStream (...)) {// Сделайте что-нибудь с x} catch (IOException ex) {// Обработайте исключение // Ресурс x автоматически закрывается} / / try
Проблемы

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

Фундаментальная проблема заключается в том, что наличие ресурса больше не является инвариантом класса (ресурс удерживается с момента создания объекта до его удаления, но объект все еще жив в этот момент), поэтому ресурс может быть недоступен, когда объект пытается его использовать, например, пытается читать из закрытого файла. Это означает, что все методы объекта, которые используют этот ресурс, потенциально терпят неудачу, обычно обычно возвращая ошибку или вызывая исключение. На практике это незначительно, так как использование ресурсов обычно может завершаться неудачно и по другим причинам (например, при попытке чтения после конца файла), поэтому эти методы уже могут дать сбой, а отсутствие ресурса просто добавляет еще один возможный сбой.. Стандартный способ реализовать это - добавить к объекту логическое поле с именем disposed, для которого установлено значение true с помощью disposeи проверяется с помощью предложения защиты для всех методов (использующих ресурс), вызывая исключение (например, ObjectDisposedExceptionв.NET), если объект был удален.

Кроме того, можно вызвать разместитьна объекте более одного раза. Хотя это может указывать на ошибку программирования (каждый объект, содержащий ресурс, должен быть удален ровно один раз), это проще, надежнее и, следовательно, обычно предпочтительно, чтобы disposeбыл идемпотентным (что означает «многократный вызов - это то же самое, что и однократный вызов»). Это легко реализовать, используя одно и то же логическое поле disposedи проверяя его в предложении защиты в начале dispose, в этом случае возвращая немедленно, а не вызывая исключение. Java отличает одноразовые типы (те, которые реализуют AutoCloseable ) от одноразовых типов, где dispose является идемпотентным (подтип Closeable ).

Удаление при наличии наследования и композиции объектов, которые содержат ресурсы, имеют проблемы, аналогичные разрушению / финализации (через деструкторы или финализаторы). Кроме того, поскольку шаблон удаления обычно не имеет языковой поддержки для этого, необходим шаблонный код . Во-первых, если производный класс переопределяет метод disposeв базовом классе, метод переопределения в производном классе обычно должен вызывать метод disposeв базовом классе, чтобы правильно освободить ресурсы, хранящиеся в базе. Во-вторых, если объект "имеет" связь с другим объектом, который содержит ресурс (т. Е. Если объект косвенно использует ресурс через другой объект, который напрямую использует ресурс), должен ли косвенно использующийся объект быть одноразовым? Это соответствует тому, является ли отношение владением (композиция объекта ) или просмотром (объединение объектов ) или даже просто взаимодействием (ассоциация ), и обнаруживаются оба соглашения (косвенный пользователь несет ответственность за ресурс или не несет ответственности). Если косвенное использование отвечает за ресурс, он должен быть одноразовым и удалять принадлежащие ему объекты при его удалении (аналогично уничтожению или завершению принадлежащих объектов).

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

См. Также
Примечания
Ссылки
Дополнительная литература
Последняя правка сделана 2021-05-17 09:06:14
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте