使用特定模式实现接口

时间:2010-09-14 11:12:27

标签: c# .net design-patterns interface

我最近对设计模式非常感兴趣,特别是在我的类中遵循正确的设计模式,实现了一个或多个接口。

我们举一个例子。当一个类实现IDisposable时,你应该遵循一个特定的模式来确保你的资源被正确清理,通过创建一个私有的Dispose(bool disposing)方法来区分它是否被终结器调用,或者它是否被调用来自公众的Dispose方法。此外,在这种情况下应该实现终结器,并且您可能还需要一个名为isDisposed的私有bool变量,该变量由Dispose方法设置,因此在对象之后调用的任何方法都是Disposed将调用异常,使其明确该对象已被处置,而不是方法内的代码崩溃,因为一些必需的资源被处置,从而不再可用。

我还经常实现很多其他接口,但并不是所有接口我确信如果我实现它们的方式是首选的方式,我可能会在稍后发现它会导致一个微妙的很难找到的错误,如果我首先遵循正确的模式,这可能是不存在的。

接口的一些例子我想知道实现的最佳方式是ISerializable,IComparable,IComparable<>,ICloneable,IEnumerable<>等等。框架中的所有接口都很有趣,因此不应局限于我上面列出的那些接口。

我所追求的是针对不同界面,首选方式,并希望也是互联网上资源的链接,解释了应该如何以及为什么要遵循特定模式。

我希望能够很好地收集这些模式,因为我知道它们可以极大地改进我的代码并使其更正确,并遵循最佳实践

如果同一界面有多种模式,那将是很好的,所以我们可以讨论哪一种是首选的。这也许会导致你们中的一些人转向新的模式,或者对你们现有的模式进行修改,以进一步改进你们的代码,那就太棒了!

修改

在阅读Grzenios评论之后,我还会敦促每个人都给出应该应用模式的上下文。例如,只有在类中需要处理某些非托管资源时才应遵循IDIsposable模式,而不是如果您需要处置的所有对象都自己实现IDisposable。

修改2

我应该自己开始,因为我在这里提出问题。所以我将描述一个我熟悉的模式,那就是IDisposable模式。

只有当您的类在您的类中包含一个或多个非托管资源时才应使用此模式,并且您必须确保它们获得Disposed。在这种情况下,除了Dispose方法之外,我们还需要一个终结器,以防你班级的用户忘记处理它。

首先是第一件事。您的类应该实现IDisposable接口,您必须通过接口将公共Dispose方法定义为goverend。此方法应如下所示:

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

这将调用受保护的Dispose(bool)方法来处理实际的清理工作。

此外,在您的班级中加入一个vaiable来表明该班级是否被处置:

private bool alreadyDisposed = false;

GC.SuppressFinalize告诉垃圾收集器,即使它有终结器,也不需要最终确定该项目。

然后你需要受保护的Dispose方法。如果任何派生类需要覆盖它,请将其保护而不是私有:

protected virtual void Dispose(bool isDisposing)
{
  if (alreadyDisposed)
  {
    return;
  }
  if (isDisposing)
  {
    // free all managed resources here
  }
  // free all unmanaged resources here.
  alreadyDisposed = true;
}

如果用户忘记清理,终结器也应该调用Dispose(bool):

~SomeClass(){
  Dispose(false);
}

如果某些方法需要配置资源才能运行,请执行以下功能:

public void SomeMethod()
{
  if (alreadyDisposed)
    throw new ObjectDisposedException("SomeClass",
                                      "Called SomeMethod on Disposed object");
  // Method body goes here
}

就是这样。这将确保资源得到清理。最好由您的类的用户调用Dispose,但通过添加Finalizer作为后备方法。

2 个答案:

答案 0 :(得分:3)

在了解设计模式的同时,您还应该看一些常见的反模式,然后了解模式的来源。 IDisposable有点像反模式,是sequential coupling的次要版本,因为它需要用户调用dispose,如果他忘记了,你就会陷入困境。 “一次性模式”的主要原因之一是解决这个问题。

一种首选的技术(无论如何),尽可能不是将IDisposable对象暴露给用户,而是暴露单个方法(例如,调用它为Using(...)),它接受一个将执行的委托通常包含在using(...) { }块中的代码。这个方法可以执行你的构造,执行委托,然后处理它所消耗的资源,并且省略IDisposable的至少3个问题:用户忘记调用它,用户多次调用它,用户也调用它早期 - 当没有IDisposable暴露时,你不需要打扰那个样板“一次性模式”。

一个例子,假设我有一个文件IO要求,我需要定期打开相同的文件(因此,如果用户忘记调用Dispose,我不能在垃圾收集器上等待调用Finalize )。

class MyFileStream {
    FileStream fs;
    private MyFileStream(string filename, FileMode mode) {
        fs = new FileStream(filename, FileMode.Open);
    }
    private void Dispose() {
        fs.Dispose();
    }
    public static void Using(string filename, FileMode mode, Action<MyFileStream> use) {
        MyFileStream mfs = new MyFileStream(filename, mode);
        use(mfs);
        mfs.Dispose();
    }
    public void Read(...) { ... }
}

然后呼叫者可以说

var x = default(...);
MyFileStream.Using("filename.txt", FileMode.Open, (stream) => {
    x = stream.Read(...);
});
Console.WriteLine(x);

请注意,这与using() { }语言语法非常相似,只是这次您被迫使用它,并且它会施加进一步的限制。人们不能忘记以这种方式清理资源。

尽管如此,这种模式并不总是合适的,因为您有时需要资源的持续时间比方法调用的时间长,但如果您有机会使用它,请执行此操作。

无论如何,我已经完全离开了主题,所以回到你的要求。

  • 不要使用ICloneable - 它没有说明如何克隆对象(深或浅),如果你需要这样的东西,可以创建自己的IDeepCloneable或IShallowCloneable接口。
  • 对于IEnumerable&lt;&gt;,这是一个罕见的事件,您需要创建自己的,因为框架中已经存在相当大的现有类集合,并且您通常可以通过实现扩展方法更简单地获得其他功能(比如LINQ或你自己的那些,利用强大的yield关键字,它将在后台为你创建一个IEnumerable&lt;&gt;)。

我不会说其余部分有任何特定的模式,它们是非常自我解释的。

答案 1 :(得分:0)

  

例如,只有当你的类中有一些你需要处理的非托管资源时才应该遵循IDIsposable模式,而不是你需要处理的所有对象都自己实现IDisposable。

我对上述情况不以为然。我从不建议允许GC进行清理并隐含对象和资源处理。这有点像等待女仆走来走去,在酒店拿起你的湿毛巾;它最终会发生,但正确的做法就是把它们捡起来自己挂起来。

确定性地处理对象并尽可能减少这些资源的范围将使得最精简和最有效的应用程序,更不用说对于阅读代码的开发人员来说,处理资源和更好地阅读更加明确。就像故事的开始和结束一样,人们可以在一个地方看到所有故事。我尽可能晚地实例化对象,并在使用最小化范围后尽快处理它。在对象上显式调用.Dispose或使用自动调用.Dispose方法的Using块是(2)清理的好方法。

  

我还经常实现很多其他接口,但并不是所有接口我确信如果我实现它们的方式是首选的方式,我可能会在稍后发现它会导致一个微妙的很难找到的错误,如果我首先遵循正确的模式,这可能是不存在的。

接口的全部目的是为类实现的方法,属性,事件等创建指导,但不提供有关如何执行的详细信息。那取决于你。如何实现specefic接口本身没有“模式”。不要被IDisposable接口抛弃。在VS.NET中创建接口后,将为您创建的流明代码对于接口而言是典型的,并且实际上可以完全更改为您所需的。

我认为您可能会感到困惑的是实现.NET Framework中的接口,但您需要查看接口的概念,而不仅仅是框架中可用的接口。如果您真的想看看.NET Framework中的接口是如何实现的示例,请查看MSDN或反编译实现这些接口的其他Framework对象,以了解Microsoft如何实现它们。

随着您在OOD中的成长,您将开始看到界面背后的力量并开始为您的课程创建它们。例如,假设您有一个名为IMachine的接口,其接口名为.StartEngine()。我们只希望接口的实现者自己定义所有的详细信息;没有“模式”可供遵循,我们接口设计师不需要知道或关心它是如何实现的。对于汽车而言,方法实施可能涉及“获取钥匙,将它们置于点火装置,放入停车位,踩下制动踏板,转动点火装置......”然而对于割草机而言,相同的方法实施是“将气体放入割草机,主要carborateor,拉动离合器,拉绳子......“

所以你看到你不能只看.NET Framework中的接口,以及如何将某种模式应用于它们的实现。实现细节,模式等取决于实现类,只要接口的细节足够,那就重要了。

希望这有点帮助...