装饰和IDisposable接口 [英] Decorators and IDisposable

查看:271
本文介绍了装饰和IDisposable接口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

的DbContext

 公共类MyContext的子类:的DbContext {} 

和我有一个 IUnitOfWork 围绕 MyContext 抽象实现的IDisposable 来保证,如 MyContext
公共类:C>在适当的时候

 公共接口IUnitOfWork布置的的UnitOfWork:IUnitOfWork 
{
私人只读MyContext _context;

公众的UnitOfWork()
{
_context =新MyContext();
}

〜的UnitOfWork()
{
的Dispose(假);
}

公共无效的Dispose()
{
的Dispose(真);
GC.SuppressFinalize(本);
}

私人布尔_disposed;

受保护的虚拟无效的Dispose(BOOL处置)
{
如果(_disposed)回报;

如果(处置)
{
如果(!_context = NULL)_context.Dispose();
}

_disposed = TRUE;
}
}



我的的UnitOfWork 与每个(网络)请求的生命周期范围内注册。我有 IUnitOfWork 的装饰,可以被注册为瞬时或终身范围和我的问题是他们应该做的关于实施的IDisposable - 特别是如果他们或他们不应通过对呼叫的Dispose()

 公共类UnitOfWorkDecorator:IUnitOfWork 
{
私人只读IUnitOfWork _decorated;

公共UnitOfWorkDecorator(IUnitOfWork饰)
{
_decorated =装饰;
}

公共无效的Dispose()
{
//我们传递的电话吗?
_decorated.Dispose();
}
}



我看到2个选项(我猜选项2是正确的答案):




  1. 预计每个装饰将知道它是否是短暂的或终生范围。如果装饰是短暂的,然后它不应该叫的Dispose()的装饰实例。如果是终身范围应该。

  2. 每个装饰应与自身处理,并应绝不会通上调用装修实例有关。容器将管理()调用的Dispose 在适当的时候调用链中的每个对象。一个对象应仅的Dispose(),它封装和装饰是不是封装的实例。


解决方案


又该[装饰]做关于实施了IDisposable





这回来到所有权的一般原则。问问你自己:谁拥有可支配的类型?。在这个问题的答案是:他谁拥有的类型是负责处理它



由于一次性类型是从外部传递到装饰,装饰没'T创建该类型,通常不应该负责清理它。装饰没有办法知道的类型是否应该进行处理(因为它没有控制其生命周期),这是你的情况很清楚,因为装饰可以注册为短暂的,而decoratee有一个更长的寿命的方法。在你的情况,如果你从装饰内出售decoratee系统只会打破。



所以装饰不应该处置decoratee,只是因为它不拥有decoratee。这是你的根组成处置该decoratee的责任。不要紧,我们在谈论在这种情况下装饰;它仍然归结于所有权的一般原则。





每个装饰应与自身处置有关并应
从来没有通上调用装修实例。





正确的。装饰应处置其拥有的东西都在,但由于您使用依赖注入,它一般不会产生太多的东西本身,因此并不拥有的东西。



你的的UnitOfWork ,另一方面创建一个新的 MyContext 类,并为此有一个实例的所有权,它应该释放吧。



有此规则的例外,但它仍然归结于所有权。有时候,你传递一种对他人的所有权。当使用工厂方法,例如,按照惯例工厂方法传中创建的对象给调用者的所有权。有时候,所有权被传递到创建的对象,如.NET的的StreamReader 类一样。 API文档是清楚这一点,但由于设计是这样直观,开发商不断绊倒此行为。大多数在.NET框架的类型,不要以这种方式工作。例如,的SqlCommand 类不处置的SqlConnection ,这将是很烦人的,如果它没有处置连接。



看待这个问题的另一种方法是从的 SOLID原则的。通过让 IUnitOfWork 实施的IDisposable 你违反的依赖倒置原则,因为抽象不应该依赖于细节,细节应该依赖于抽象。通过实施的IDisposable 您正在泄漏的实施细则到 IUnitOfWork 接口。实施的IDisposable 意味着该类已非托管需要处理的资源,如文件句柄和连接字符串。这些是实施细节,因为它不能未落是这样的接口的每个实现实际需要弃置于所有的情况。你只需要创建一个假的或者模拟实现了单元测试,你有没有需要处理实现的证明。



所以,当你解决这个问题DIP违规从 IUnitOfWork 删除的IDisposable 接口 - 和其移动到implementation-,就不可能为装饰处置在decoratee,因为它无法知道与否的decoratee工具的IDisposable 的方式。这是很好的,因为根据DIP,装饰不应该知道 - 和 - 我们已经建立的装饰不应该处置decoratee。


I have a subclass of DbContext

public class MyContext : DbContext { }

and I have an IUnitOfWork abstraction around MyContext that implements IDisposable to ensure that references such as MyContext are disposed of at the appropriate time

public interface IUnitOfWork : IDisposable { }

public class UnitOfWork : IUnitOfWork 
{
    private readonly MyContext _context;

    public UnitOfWork()
    {
        _context = new MyContext();
    }

    ~UnitOfWork()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private bool _disposed;

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            if (_context != null) _context.Dispose();
        }

        _disposed = true;
    }
}

My UnitOfWork is registered with a lifetime scope of per (web) request. I have decorators of IUnitOfWork that could be registered as transient or lifetime scoped and my question is what should they do with regard to implementing IDisposable - specifically should they or should they not pass on the call to Dispose().

public class UnitOfWorkDecorator : IUnitOfWork
{
    private readonly IUnitOfWork _decorated;

    public UnitOfWorkDecorator(IUnitOfWork decorated)
    {
        _decorated = decorated;
    }

    public void Dispose()
    {
        //do we pass on the call?
        _decorated.Dispose();
    }
}    

I see 2 options (I'm guessing option 2 is the correct answer):

  1. It is expected that each Decorator will know whether it is transient or lifetime scoped. If a decorator is transient then it should not call Dispose() on the decorated instance. If it is lifetime scoped it should.
  2. Each decorator should only be concerned with disposing of itself and should never pass on the call to the decorated instance. The container will manage the call to Dispose() for each object in the call chain at the appropriate time. An object should only Dispose() of instances that it encapsulates and decorating is not encapsulation.

解决方案

what should [decorators] do with regard to implementing IDisposable

This comes back to the general principle of ownership. Ask yourself: "who owns that disposable type?". The answer to this question is: He who owns the type is responsible for disposing it.

Since a disposable type is passed on to the decorator from the outside, the decorator didn't create that type and should normally not be responsible for cleaning it up. The decorator has no way of knowing whether the type should be disposed (since it doesn't control its lifetime) and this is very clear in your case, since the decorator can be registered as transient, while the decoratee has a much longer lifetime. In your case your system will simply break if you dispose the decoratee from within the decorator.

So the decorator should never dispose the decoratee, simply because it doesn't own the decoratee. It's the responsibility of your Composition Root to dispose that decoratee. It doesn't matter that we're talking about decorators in this case; it still comes down to the general principle of ownership.

Each decorator should only be concerned with disposing of itself and should never pass on the call to the decorated instance.

Correct. The decorator should dispose everything it owns though, but since you're using dependency injection, it typically doesn't create much stuff itself and therefore doesn't own that stuff.

Your UnitOfWork on the other hand creates a new MyContext class and therefor has the ownership of that instance and it should dispose it.

There are exceptions to this rule, but it still comes down to ownership. Sometimes you do pass on ownership of a type to others. When using a factory method for instance, by convention the factory method passes on the ownership of the created object to the caller. Sometimes ownership is passed on to a created object, such as .NET's StreamReader class does. The API documentation is clear about this, but since the design is such unintuitive, developers keep tripping over this behavior. Most of the types in the .NET framework don't work this way. For instance, the SqlCommand class doesn't dispose the SqlConnection, and it would be very annoying if it did dispose the connection.

A different way of looking at this issue is from perspective of the SOLID principles. By letting the IUnitOfWork implement IDisposable you are violating the Dependency Inversion Principle, because "Abstractions should not depend on details; Details should depend on abstractions". By implementing IDisposable you are leaking implementation details into the IUnitOfWork interface. Implementing IDisposable means that the class has unmanaged resources that need disposal, such as file handles and connection strings. These are implementation details, because it can't hardly ever be the case that each implementation of such interface actually needs disposal at all. You just have to create one fake or mock implementation for your unit tests and you have proof of an implementation that doesn't need disposal.

So when you fix this DIP violation by removing the IDisposable interface from IUnitOfWork -and moving it to the implementation-, it becomes impossible for the decorator to dispose the decoratee, because it has no way of knowing whether or not the decoratee implements IDisposable. And this is good, because according to the DIP, the decorator shouldn't know -and- we already established that the decorator should not dispose the decoratee.

这篇关于装饰和IDisposable接口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆