Шаблон утилизации - Dispose pattern

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

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

Мотивация

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

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

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

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

Например, в Ввод / вывод файла C, файлы представлены объектами ФАЙЛ тип (сбивчиво названный "файловые ручки ": это абстракция на уровне языка), в которой хранится дескриптор (операционной системы) файла (например, дескриптор файла ) вместе со вспомогательной информацией, такой как режим ввода-вывода (чтение, запись) и положение в потоке. Эти объекты создаются путем вызова fopen (в объектно-ориентированных терминах конструктор ), который получает ресурс и возвращает указатель на него; ресурс освобождается путем вызова fclose по указателю на ФАЙЛ объект.[1] В коде:

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

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

ж = открыто(имя файла)# Сделайте что-нибудь с f.ж.Закрыть()

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

Оперативный выпуск

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

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

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

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

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

Таким образом, не связывая управление ресурсами с временем жизни объекта, шаблон удаления позволяет Ресурсы должны быть выпущены незамедлительно, обеспечивая гибкость реализации для управления памятью. Цена этого заключается в том, что ресурсами нужно управлять вручную, что может быть утомительным и подверженным ошибкам.

Ранний выход

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

Например:

def func(имя файла):    ж = открыто(имя файла)    если а:        возвращаться Икс    ж.Закрыть()    возвращаться у

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

def func(имя файла):    ж = открыто(имя файла)    грамм(ж)  # Сделайте что-нибудь с f, что может вызвать исключение.    ж.Закрыть()

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

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

def func(имя файла):    пытаться:        ж = открыто(имя файла)        # Сделай что-нибудь.    наконец-то:        ж.Закрыть()

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

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

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

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

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

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

В C # язык особенности с помощью утверждение [2] который автоматически вызывает Утилизировать на объекте, который реализует IDisposable интерфейс:

с помощью (Ресурс ресурс = GetResource()){    // Выполняем действия с ресурсом.    ...}

что равно:

Ресурс ресурс = GetResource()пытаться {    // Выполняем действия с ресурсом.    ...}наконец-то {    // Ресурс может не быть получен или уже освобожден    если (ресурс != ноль)         ((IDisposable)ресурс).Утилизировать(); }

Точно так же Python язык имеет с заявление, которое может быть использовано с аналогичным эффектом с менеджер контекста объект. В протокол диспетчера контекста требует реализации __войти__ и __выход__ методы, которые автоматически вызываются с конструкция оператора, чтобы предотвратить дублирование кода, которое иначе могло бы произойти с пытаться/наконец-то шаблон.[3]

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

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

пытаться (OutputStream Икс = новый OutputStream(...)) {    // Делаем что-нибудь с x} ловить (IOException бывший) {    // Обработка исключения  // Ресурс x автоматически закрывается} // пытаться

Проблемы

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

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

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

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

Состав (владение) обеспечивает инкапсуляция (необходимо отслеживать только используемый объект), но за счет значительной сложности, когда между объектами существуют дальнейшие связи, в то время как агрегирование (просмотр) значительно проще за счет отсутствия инкапсуляции. В .СЕТЬ, соглашение заключается в том, чтобы отвечать только за непосредственного пользователя ресурсов: «Вы должны реализовать IDisposable только в том случае, если ваш тип напрямую использует неуправляемые ресурсы».[6] Видеть Управление ресурсами подробности и другие примеры.

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

Примечания

  1. ^ В программирование на основе классов, методы определены в классе с использованием неявного это или же себя параметр, а не как функции, принимающие явный параметр.

Рекомендации

  1. ^ stdio.h - Справочник по базовым определениям, Единая спецификация UNIX, Выпуск 7 из Открытая группа
  2. ^ Microsoft MSDN: Оператор using (Справочник по C #)
  3. ^ Гвидо ван Россум, Ник Коглан (13 июня 2011 г.). «PEP 343: The» с «Заявлением». Фонд программного обеспечения Python.
  4. ^ Учебник Oracle Java: Заявление о попытках с ресурсами
  5. ^ а б c "Удалить шаблон".
  6. ^ "IDisposable Интерфейс". Получено 2016-04-03.

дальнейшее чтение