一次性上下文对象模式

介绍

我只是想到了一种新的设计模式。 我想知道它是否存在,如果不存在,为什么不(为什么我不应该使用它)。

我正在使用OpenGL创建游戏。 在OpenGL中,你经常想要“绑定”一些东西 - 例如,使它们成为当前的上下文一段时间,然后解除它们。 例如,你可以调用glBegin(GL_TRIANGLES)然后绘制一些三角形,然后调用glEnd() 。 我喜欢缩进其中的所有内容,以便清楚它开始和结束的位置,但是随后我的IDE喜欢取消它们,因为没有大括号。 然后我认为我们可以做一些聪明的事情! 它基本上是这样工作的:

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`
}

但在这种情况下,我们不需要在我们的'used'对象上调用任何方法,所以我们不需要将它分配给任何东西,除了调用相应的结束函数之外,它没有其他用途。


对于Anthony Pegram,他想要一个我正在研究的代码的真实示例:

重构之前:

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);
    }
}

请注意, _ibo.Bind返回的对象还记得我想要解除绑定的“BufferTarget”的第二个好处。 它也吸引了你对GL.DrawElements ,它是该函数中唯一重要的语句(它做了一些注意事项),并隐藏了那些冗长的解除绑定语句。

我猜想一个缺点是我不能用这种方法使缓冲区目标交错。 我不知道什么时候会想要,但是我必须保留对绑定对象的引用,并手动调用Dispose或手动调用结束函数。


命名

如果没有人反对,我会配音这个Disposable Context Object(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()你的上下文对象,那么没有任何后果。 上下文不会切换回原来的状态。 现在,如果你忘记在某种循环内部处理它,你会得到一个计算器。 也许我应该限制堆栈大小......


Asp.Net MVC与HtmlHelper一起使用了类似的策略。 请参阅http://msdn.microsoft.com/en-us/library/system.web.mvc.html.formextensions.beginform.aspx( using (Html.BeginForm()) {....}

所以至少有一个使用这种模式的例子,除了文件句柄,数据库或网络连接,字体等非托管资源的IDisposable的明显“需求”以外。 我不认为它有一个特殊的名字,但实际上,它似乎是C#习惯用法,它与C ++习惯用法是对等的,即资源获取是初始化。

当你打开一个文件时,你正在获取并保证文件上下文的处理; 在你的例子中,你所获得的资源是一个“绑定上下文”,用你的话来说。 虽然我听说用于描述广泛类别的“处理模式”或“使用模式”,但本质上“确定性清理”就是您正在谈论的内容; 你正在控制对象的生命周期。

我不认为它真的是一种“新”模式,它在你的用例中突出的唯一原因是显然你所依赖的OpenGL实现没有做出特别的努力来匹配C#语言,这需要你建立你自己的代理对象。

我担心的唯一情况是,如果存在任何非明显的副作用,例如,如果您的嵌套上下文中存在块(或调用堆栈)中较深的相似using构造。


ASP.NET / MVC使用这个(可选)模式来渲染一个<form>元素的开始和结束,如下所示:

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

这与您的示例类似,因为除了其一次性语义之外,您不会消耗您的IDisposable的价值。 我从来没有听说过这个名字,但是我之前在其他类似的场景中使用过这种类型的东西,除了理解如何通常利用带有IDisposableusing块以外,我从来没有把它看作任何其他类似于我们可以点击通过实现IEnumerable进入foreach语义。


我认为这更像是一种成语,而不是一种模式。 模式通常比较复杂,涉及多个移动部分,而成语只是在代码中做事情的聪明方式。

在C ++中它被使用了很多。 无论何时你想获得某物或输入一个范围,你都会创建一个开始或创建的类的自动变量(即在堆栈中),或者你在入门时需要做的任何事情。 当你离开声明自动变量的范围时,调用析构函数。 然后析构函数应该结束或删除或清理所需的任何东西。

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.
}
链接地址: http://www.djcxy.com/p/54037.html

上一篇: Disposable Context Object pattern

下一篇: Should I call Close() or Dispose() for stream objects?