轻量级和工厂模式中的IDisposable问题

11

我似乎陷入了享元模式的思维僵局。

首先,假设我有一个可丢弃类型 DisposableFiddle 和一个工厂 FiddleFactory

public interface DisposableFiddle : IDisposable
{
    // Implements IDisposable
}

public class FiddleFactory
{
    public DisposableFiddle CreateFiddle(SomethingThatDifferentiatesFiddles s)
    {
        // returns a newly created fiddle.
    }
}

在我看来,FiddleFactory的客户端非常清楚工厂不声称对创建的fiddle拥有所有权,当使用完毕后,由客户端负责处理fiddle的处置。

然而,假设我想通过使用享元模式在客户端之间共享fiddles:

public class FiddleFactory
{
    private Dictionary<SomethingThatDifferentiatesFiddles, DisposableFiddle> fiddles = new ...;        

    public DisposableFiddle CreateFiddle(SomethingThatDifferentiatesFiddles s)
    {
        // returns an existing fiddle if a corresponding s is found,
        // or a newly created fiddle, after adding it to the dictionary,
        // if no corresponding s is found.
    }
}

我感到在道德上有义务使工厂本身可被处理(disposable),因为它创建了小提琴(fiddles)并在其整个生命周期中保留对它们的引用。但这将会给那些假定拥有小提琴并应该处置它们的客户带来问题。

实际上,问题是我将工厂称为 FiddleFactory 而不是例如 FiddlePool,将 "创建" 方法称为 CreateFiddle 而不是 GetFiddle 吗?像这样:

public class FiddlePool : IDisposable
{
    private Dictionary<SomethingThatDifferentiatesFiddles, DisposableFiddle> fiddles = new ...;        

    public DisposableFiddle GetFiddle(SomethingThatDifferentiatesFiddles s)
    {
        // returns an existing fiddle if a corresponding s is found,
        // or a newly created fiddle, after adding it to the dictionary,
        // if no corresponding s is found.
    }

    // Implements IDisposable
}

那么对客户端来说,更明确的是它不会拥有返回的 "fiddle" 对象,而这是池的责任来处理这些对象的清理工作。

或者这只能通过文档来解决吗?

有没有一种方法可以解决这个困境?甚至有困境存在吗? :-)

3个回答

7

我可以看到两种解决这个问题的方法:

  • 线程池风格:重新设计类,使FiddlePool提供一个接口来做琐碎的事情。池不会分发Fiddle实例,因为它有一个FiddlePool.PlayFiddle方法。由于池控制着Fiddle的生命周期,因此它负责处理他们的释放。

  • SqlConnection风格:修改Fiddle的公共dispose方法,以便它真正地将Fiddles返回到封装了fiddle池的fiddle类中。在内部,fiddle池负责真正地释放可处置资源。


谢谢,第一个更符合洛德米特法则,而第二个更适合我的整体设计。嗯... - Johann Gerell
我会为第一种方法提出我的意见 - 我已经看到太多人试图编写自己的SqlConnnection池!(实际上并不是,但我确实不得不解释为什么这是不必要的。) - Jeff Sternal

2

我同意你的第二个观点。“Pool”和“Get”这样的术语确实可以让消费者更加清晰地了解事情。然而,这还不足够清晰明了,为了确保全面有效的理解,文档应该始终被添加。


3
完全同意。此外,我可能不会把你的类命名为“DisposableFiddle”,除非你希望用户调用Dispose()方法。 - Reed Copsey
@Reed:同意 DisposableFiddle 的观点。适当的命名对于传达意图至关重要,也是防止错误使用的第一道防线。 - jrista
嘿,是的,我只是在示例名称前加了“一次性”的前缀,以便在我(混乱)的步骤中更容易跟踪... - Johann Gerell

1

你应该做更多的事情来告诉客户不要调用dispose,而不仅仅是文档和方法命名。实际上,最好让客户调用dispose,这样使用模式会更好。可以从我们的数据库连接池中获取一些指导。

数据库池化了一堆连接,这些连接本身都是池感知的。调用代码创建一个连接,打开它,并在其上调用close(dispose)。调用代码甚至不知道它是否被池化,所有这些都由连接类在内部处理。对于池化的连接,如果连接已经打开,则忽略调用Open()。Close()/Dispose()被调用,在池化连接的情况下,这实际上将连接返回到池中而不是关闭它。

您可以通过创建一个PooledFiddle类来执行相同的操作,该类覆盖Dispose并将对象返回到池中。理想情况下,客户端甚至不需要知道它是一个池化的Fiddle。


理想情况下,客户端甚至不需要知道它是一个池化的 Fiddle。- 是的,我同意这一点。我认为这是一个更好隐藏的实现细节。 - Johann Gerell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接