Csharp/C#教程:装饰者和IDisposable分享


装饰者和IDisposable

我有一个DbContext的子类

 public class MyContext : DbContext { } 

我在IUnitOfWork周围有一个IUnitOfWork抽象,它实现了IDisposable以确保在适当的时候处理诸如MyContext类的引用

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

我的UnitOfWork注册了每(web)请求的生命周期范围。 我有IUnitOfWork装饰器,可以注册为瞬态或生命周期范围,我的问题是他们应该如何实现IDisposable – 特别是他们应该或不应该传递对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(); } } 

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

  1. 预计每个Decorator将知道它是瞬态还是寿命范围。 如果装饰器是瞬态的,那么它不应该在装饰实例上调用Dispose() 。 如果它是终身范围的,它应该。
  2. 每个装饰器应该只关心处理它自己,并且永远不应该将调用传递给装饰实例。 容器将在适当的时间管理对调用链中每个对象的Dispose()调用。 一个对象应该只有Dispose()它封装的实例和装饰不是封装。

[装饰者]应该如何实现IDisposable

这又回到了所有权的一般原则。 问问自己:“谁拥有那种一次性用品?” 这个问题的答案是:拥有该类型的人负责处理它。

由于一次性类型从外部传递到装饰器,装饰器不会创建该类型,通常不应负责清理它。 装饰者无法知道是否应该处理类型(因为它不能控制其生命周期),这在你的情况下非常清楚,因为装饰器可以注册为瞬态,而decoratee具有更长的寿命。 在你的情况下,如果你从装饰器中处理decoratee,你的系统将会破坏。

所以装饰者永远不应该丢弃decoratee,因为它不拥有decoratee。 你的作品根负责处理那个装饰。 在这种情况下我们谈论装饰器并不重要; 它仍然归结为所有权的一般原则。

每个装饰器应该只关心处理它自己,并且永远不应该将调用传递给装饰实例。

正确。 装饰者应该处理它拥有的所有东西,但是由于你正在使用dependency injection,它通常不会创建很多东西,因此不拥有那些东西。

另一方面,你的UnitOfWork创建了一个新的MyContext类,因此它具有该实例的所有权,它应该处理它。

这条规则有例外,但它仍然归结为所有权。 有时您会将某种类型的所有权传递给其他人。 例如,当使用工厂方法时,按照惯例,工厂方法将创建的对象的所有权传递给调用者。 有时将所有权传递给创建的对象,例如.NET的StreamReader类。 API文档很明确,但由于设计非常不直观,开发人员不断绊倒这种行为。 .NET框架中的大多数类型都不能以这种方式工作。 例如, SqlCommand类不会处理SqlConnection ,如果它确实处​​理了连接,那将非常烦人。

从SOLID原则的角度来看,讨论这个问题的另一种方式。 通过让IUnitOfWork实现IDisposable你违反了依赖性倒置原则 ,因为“抽象不应该依赖于细节;细节应该依赖于抽象”。 通过实现IDisposable您将实现细节泄漏到IUnitOfWork接口中。 实现IDisposable意味着该类具有需要处理的非托管资源,例如文件句柄和连接字符串。 这些是实现细节,因为根本不可能实际上需要处理这种接口的每个实现。 您只需为unit testing创​​建一个伪造或模拟实现,并且您可以获得不需要处理的实现certificate。

因此,当您通过从IUnitOfWork删除IDisposable接口IUnitOfWork移动到实现来修复此DIP违规时,装饰器将无法处置decoratee,因为它无法知道decoratee是否实现了IDisposable 。 这很好,因为根据DIP,装饰者不应该知道 – 并且 – 我们已经确定装饰者不应该处理decoratee。

不是答案,但您的UnitOfWork可以简化很多。

按照这些步骤操作时,这就是UnitOfWork类型的剩余部分:

 public sealed class UnitOfWork : IUnitOfWork, IDisposable { private readonly MyContext _context; public UnitOfWork() { _context = new MyContext(); } public void Dispose() { _context.Dispose(); } } 

如果您通过将MyContext注入UnitOfWork而将其从UnitOfWork移出,您甚至可以将UnitOfWork简化为以下内容:

 public sealed class UnitOfWork : IUnitOfWork { private readonly MyContext _context; public UnitOfWork(MyContext context) { _context = context; } } 

由于UnitOfWork接受MyContext它没有所有权,因此不允许处置MyContext (因为即使在UnitOfWork超出范围之后,另一个消费者可能仍然需要使用它)。 这意味着UnitOfWork不需要处理任何东西,因此不需要实现IDisposable

这当然意味着我们将处理MyContext的责任MyContext给“其他人”。 这个“某人”通常也是控制UnitOfWork的创建和处理的同一个人。 通常这是组合根 。

就个人而言,我怀疑你需要根据具体情况处理这个问题。 一些装饰者可能有充分的理由去理解范围; 对于大多数人来说,简单地传递它可能是一个很好的默认值。 很少有人应该明确地从不处理链 – 我看到它的主要时刻是专门用来抵消另一个应该考虑确定范围的装饰者的情况:没有(总是丢弃)。

作为一个相关的例子 – 考虑像GZipStream这样的GZipStream – 对于大多数人来说,他们只处理一个逻辑块 – 所以默认“处理流”是好的; 但是这个决定是通过构造函数重载提供的 ,它让你告诉它如何表现。 在具有可选参数的C#的最新版本中,这可以在单个构造函数中完成。

选项2是有问题的,因为它要求您(或容器)跟踪所有中间对象; 如果你的容器方便,那么很好 – 但也要注意它们必须以正确的顺序 (从外到内)处理。 因为在装饰器链中,可能存在挂起的操作 – 计划在请求时向下游刷新,或者(在处置期间)作为最后的手段。

上述就是C#学习教程:装饰者和IDisposable分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/cdevelopment/953652.html

(0)
上一篇 2021年11月19日
下一篇 2021年11月19日

精彩推荐