Шаблон одноразового контекстного объекта - PullRequest
1 голос
/ 15 января 2012

Введение

Я только что подумал о новом шаблоне дизайна.Мне интересно, существует ли оно, а если нет, то почему (или почему я не должен его использовать).

Я создаю игру с использованием OpenGL.В OpenGL вы часто хотите «связать» вещи - т.е. сделать их текущим контекстом на некоторое время, а затем отменить их привязку.Например, вы можете позвонить glBegin(GL_TRIANGLES), затем нарисовать несколько треугольников, а затем позвонить glEnd().Мне нравится делать отступы между этими вещами, чтобы было ясно, где они начинаются и заканчиваются, но тогда моя среда разработки любит удалять их, потому что нет скобок.Тогда я подумал, что мы могли бы сделать что-нибудь умное!В основном это работает так:

using(GL.Begin(GL_BeginMode.Triangles)) {
   // draw stuff
}

GL.Begin возвращает специальный объект DrawBind (с внутренним конструктором) и реализует IDisposable, так что он автоматически вызывает GL.End() в конце блока,Таким образом, все остается хорошо выровненным, и вы не можете забыть вызвать end ().

Есть ли имя для этого шаблона?

Обычно, когда я вижу using используется, вы используетеэто так:

using(var x = new Whatever()) {
   // do stuff with `x`
}

Но в этом случае нам не нужно вызывать какие-либо методы для нашего «используемого» объекта, поэтому нам не нужно присваивать его чему-либо, и это не имеет смыслакроме вызова соответствующей конечной функции.


Пример

Для Энтони Пеграм , которому нужен реальный пример кода, над которым я сейчас работаю:

Перед рефакторингом:

public void Render()
{
    _vao.Bind();
    _ibo.Bind(BufferTarget.ElementArrayBuffer);
    GL.DrawElements(BeginMode.Triangles, _indices.Length, DrawElementsType.UnsignedInt, IntPtr.Zero);
    BufferObject.Unbind(BufferTarget.ElementArrayBuffer);
    VertexArrayObject.Unbind();
}

После рефакторинга:

public void Render()
{
    using(_vao.Bind())
    using(_ibo.Bind(BufferTarget.ElementArrayBuffer))
    {
        GL.DrawElements(BeginMode.Triangles, _indices.Length, DrawElementsType.UnsignedInt, IntPtr.Zero);
    }
}

Обратите внимание, что объект получает 2-е преимуществовозвращенный _ibo.Bind также запоминает, какой "BufferTarget" я хочу отменить.Это также привлекает ваше внимание к GL.DrawElements, который на самом деле является единственным значительным утверждением в этой функции (которое делает что-то заметное), и скрывает эти длинные операторы unbind.

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


Именование

Если никто не возражает, я дублирую эту идиому Одноразовый объект контекста (DCO) .


Проблемы

JasonTrue поднял хорошую мысль, что в этом сценарии (буферы OpenGL), вложенные с помощью операторов, не будут работать должным образом, так как только один буфер можетбыть связанным за один раз.Однако это можно исправить, расширив «объект связывания», чтобы использовать стеки:

public class BufferContext : IDisposable
{
    private readonly BufferTarget _target;
    private static readonly Dictionary<BufferTarget, Stack<int>> _handles;

    static BufferContext()
    {
        _handles = new Dictionary<BufferTarget, Stack<int>>();
    }

    internal BufferContext(BufferTarget target, int handle)
    {
        _target = target;
        if (!_handles.ContainsKey(target)) _handles[target] = new Stack<int>();
        _handles[target].Push(handle);
        GL.BindBuffer(target, handle);
    }

    public void Dispose()
    {
        _handles[_target].Pop();
        int handle = _handles[_target].Count > 0 ? _handles[_target].Peek() : 0;
        GL.BindBuffer(_target, handle);
    }
}

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

Ответы [ 3 ]

2 голосов
/ 15 января 2012

Аналогичная тактика используется с Asp.Net MVC с HtmlHelper.См. http://msdn.microsoft.com/en-us/library/system.web.mvc.html.formextensions.beginform.aspx (using (Html.BeginForm()) {....})

Так что есть хотя бы один прецедент использования этого шаблона для чего-то другого, кроме очевидной «потребности» в IDisposable для неуправляемых ресурсов, таких как дескрипторы файлов, базы данных или сетевые подключения, шрифты и так далее.Я не думаю, что есть специальное название для него, но на практике это, кажется, идиома C #, которая служит аналогом идиомы C ++, Resource Acquisition is Initialization.

Когда вы открываетефайл, вы приобретаете и гарантируете удаление контекста файла;в вашем примере ресурс, который вы приобретаете, является, по вашим словам, «связующим контекстом».Хотя я слышал, что «Dispose pattern» или «Using pattern» используется для описания широкой категории, по сути, вы говорите о «детерминированной очистке»;вы управляете временем жизни объекта.

Я не думаю, что это действительно "новый" шаблон, и единственная причина, по которой он выделяется в вашем случае использования, заключается в том, что, очевидно, реализация OpenGL зависит от вас.я не приложил особых усилий, чтобы соответствовать идиомам C #, что требует от вас создания собственного прокси-объекта.

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

1 голос
/ 15 января 2012

Я бы сказал, что это скорее идиома, чем шаблон.Шаблоны обычно являются более сложными, включающими несколько движущихся частей, и идиомы - просто умные способы сделать что-то в коде.

В C ++ он используется довольно часто.Всякий раз, когда вы хотите получить что-то или ввести область действия, вы создаете автоматическую переменную (т.е. в стеке) класса, который начинается или создает или все, что вам нужно сделать при входе.Когда вы покидаете область, в которой объявлена ​​автоматическая переменная, вызывается деструктор.Затем деструктор должен end или удалить или все, что требуется для очистки.

class Lock {
private:

  CriticalSection* criticalSection;

public:

  Lock() {
    criticalSection = new CriticalSection();
    criticalSection.Enter();
  }

  ~Lock() {
    criticalSection.Leave();
    delete criticalSection;
  }

}

void F() {
  Lock lock();

  // Everything in here is executed in a critical section and it is exception safe.
}
1 голос
/ 15 января 2012

ASP.NET / MVC использует этот (необязательный) шаблон для отображения начала и конца элемента <form> следующим образом:

@using (Html.BeginForm()) {
    <div>...</div>
}

Это похоже на ваш пример в том смысле, что вы не потребляете значение вашего IDisposable, кроме как для его одноразовой семантики. Я никогда не слышал об этом названии, но раньше я использовал подобные вещи в других подобных сценариях и никогда не рассматривал его как что-то кроме понимания того, как обычно использовать блок using с IDisposable, подобным как мы можем задействовать foreach семанатику, реализовав IEnumerable.

...