14

.Net 框架中的大量类被标记为“密封”,从而阻止您用自己的类继承这些类。当然,这违背了面向对象的本质,您可以在其中扩展和重新定义现有对象的行为。

“密封”关键字的存在是否有充分的理由?

例如,Silverlight 中的 NotifyCollectionChangedEventArgs 是密封的。我想创建自己的支持 AddRange 和 RemoveRange 的 ObservableCollection 版本,但是 NCCEA 的 Silverlight 版本不提供支持 NewItems 和 OldItems 属性的多个项目的构造函数,这些项目已经定义为 IList。通常,我只是用我自己的变体来扩展这个类,它覆盖了 NewItems 和 OldItems 属性,但在这种情况下,我不能,而且我看不出为什么会这样。

4

9 回答 9

24

设计可扩展的类(或框架)并非易事,简单地说,继承并不是面向对象编程的单一原则。

因此sealed存在允许开发人员/设计师表达和保留这些意图。密封类还可以通过减少维护负担让他们的生活更轻松。它允许原始开发人员控制类(或框架)的扩展方式,因此他们可以进行内部更改而不必担心破坏其他代码的更改。

一个原则是开发人员应该默认密封任何类。然后,当开发人员有意创建一个未密封的类时,它会迫使他们考虑可扩展性。


参考:Eric Lippert -为什么这么多框架类是密封的?

于 2009-02-16T22:59:33.767 回答
7

我今天问的一个有点相关的问题的答案可能有助于澄清密封类的目的:

在我开始研究自己的可重用库之前,我发现自己也在问同样的问题。很多时候,如果不要求实现者的晦涩或神秘的调用序列,您就无法扩展某些类。

当允许扩展你的类时,你必须问:如果开发人员扩展了我的类,并将这个新类传递给我的库,我可以透明地使用这个新类吗?我可以正常使用这个新课程吗?这个新班级真的会表现得一样吗?

我发现大多数时候 .Net 框架中的密封类具有某些您不知道的底层要求,并且鉴于当前的实现不能安全地暴露给子类。

Liskov 替换和组合

现在点击该链接并为实际作者投票。

于 2009-02-16T22:50:15.863 回答
2

简短的回答是,因为微软是这么说的。更长的答案是微软提供了一种扩展密封类的机制,称为扩展方法。

一般来说,扩展没有源代码的类是个坏主意。例如,你不知道调用基方法对对象的内部数据做了什么。是的,您可以使用反射器或其他任何东西来解决它,但通常使用组合或扩展方法要好得多。

您还必须考虑继承实际上是什么。它不仅仅是一种改变类的方法,它还提供了多态性。如果您更改字符串类的语义,然后将新的字符串类传递给期望字符串以特定方式起作用的对象,该怎么办?密封本质上强制执行该对象将始终按照您期望的方式工作的合同。

于 2009-02-16T22:49:58.563 回答
2

无需深入挖掘,了解 Microsoft 倾向于在存在潜在的安全性、可维护性或向后兼容性问题而必须在下游处理时密封一个类。例如,System.String出于安全和性能原因而被密封。

在这种特殊情况下,您需要询问 Microsoft 开发人员为什么选择密封该类。然而,我最近阅读的架构指导文献倾向于支持“除非你知道它需要扩展,否则密封”的方法。该文献倾向于在可能的情况下使用扩展方法。(我并不是说我同意它;我只是说这就是我最近一直在阅读的内容。)

即使该类没有密封,所讨论的属性也可能不是虚拟的,这仍然会让您陷入困境。

在您的特定情况下,我会使用具有您自己唯一名称的扩展方法。这是关于你能做的一切。

于 2009-02-16T22:55:07.083 回答
1

虽然我同意 .NET 可能密封太多,但它通常是为了保护生态系统的完整性。当您的首要任务之一是保持整体框架/API/运行时稳定,并且类之间存在一些脆弱的相互依赖关系时,防止人们覆盖该行为并无意中破坏核心功能可能是最安全的。

尽管如此,我确实倾向于认为 .NET 团队封装了太多类。密封有时可能只是开发人员的懒惰,因为适当的类设计工作量太大。

于 2009-02-16T22:50:02.400 回答
1

我建议除了保护无知的错误之外,没有充分的理由封闭课程,我的意思是无辜的。你知道那句老话,“给他们足够的绳索,他们就会上吊”。让他们动摇我说。也许这是我的 C++ 背景,但我很自在地知道,如果我不勤奋,我有能力把事情完全塞满。

我倾向于通过界面编程。接口当然是公共的,任何人都可以自由地提供自己的实现,以遵守接口所表达的约定。我实现这些接口的具体类往往是私有的,并被标记为内部和/或私有。我不觉得我需要密封这样的课程。

在需要代码重用的地方,我避免通过继承重用,偏爱组合和其他技术。

密封也可能对被认为是普通旧数据类型的类型有效,但我不相信这种方式会枯萎。

于 2009-02-16T23:33:34.200 回答
0

视情况而定,有些类打算被实例化(继承,如果存在,仅用于简化实现),其他类打算被继承以提供特定的实现。

密封类有一些优点:

  • 他们没有任何虚拟方法,因此他们不必担心非“异常安全”实现的覆盖方法。
  • 如果一个类是不可变的,它可以保持和保证不变性。

否则,如果您想用“舒适”方法装饰密封类,请使用扩展方法 (C# 3.0)。

于 2009-02-16T23:00:43.733 回答
0

仅仅因为某物是一个对象,并不意味着它应该总是对每个人都敞开大门来扩展或重新定义它的行为。

一个很好的类比是门和锁。门的本质是让人通过,这就是它的目的。然而,大多数门都设计有锁,可以限制人们通过它们的能力。决定一扇门是否有锁以及该锁是否默认锁定由房间的建筑师决定,取决于房间里有什么以及谁应该可以访问它,而不是它是一扇门的事实。

当然,如果特定建筑物中的大多数门默认都被锁定,这可能会令人沮丧,特别是如果你真的想通过一扇门。:-) 我自己去过那里,我也问过同样的问题。简短的回答是,有时在设计一个复杂的框架时,人们往往会在更谨慎的方面犯一些错误,并且默认情况下密封类,除非有明确的场景需要扩展它们。

于 2009-02-16T23:13:29.287 回答
0

我发现sealed 关键字有用的实际用途之一是避免“脆弱的类问题”。这是一个视频,显示了一个脆弱的基类问题http://www.youtube.com/watch?v=xnA3RUJcyY4

让我详细解释一下。

考虑下面的场景,我们有一个名为“DbParent”的父类和一个虚拟方法“Insert”。

类 DbParent {

    public virtual void Insert()
    {
        Console.WriteLine("Parent insert");
    }

}

下面是一个简单的子类,它有自己的“插入”实现,安装在不同的位置。

  class DbChildClass : DbParent
  {
  public void  Insert()
    {
        Console.WriteLine("Child inserts");
    }

}

现在让我们说,经过几个月的部署,父类开发人员在不了解对子类的影响的情况下,去添加一个新方法“Add”。这个“添加”方法在内部调用“插入”方法(下面是相同的代码片段)。

  class DbParent
  {
  public  void Add() // Adds method with out consulting
  {
  this.Insert();
  }

  public virtual void Insert()
  {
        Console.WriteLine("Parent insert");
  }
  }

现在,通过创建子类对象调用“Add”方法的客户端程序期望调用“Child”类“Insert”实现。

  DbChildClass o = new DbChildClass();
  o.Add();
  Console.Read();

但是哇哦,如果你运行下面的代码,你会看到父类“Insert”被调用,这不是预期的。

所以我会把课程标记为“密封”以避免此类问题。

于 2013-07-27T12:28:41.933 回答