设计继承如何导致额外的费用?[关闭]


12

所以,我想继承一个sealed classCSHARP,结果被烧毁。除非您有权访问源,否则无法解封。

然后,我想到了“为什么sealed甚至存在?”。4个月前。尽管阅读了很多有关内容的文章,但我还是不知道,例如:

从那以后我一直试图消化所有这些,但是对我来说太过分了。最终,昨天我再次尝试了。我再次扫描了所有这些文件,还有更多:

最后,经过深思熟虑,我决定根据新标题对原始问题进行大量编辑。

老问题过于宽泛和主观的。基本上是在问:

  1. 旧标题中:使用密封的一个很好的理由
  2. 在正文中:如何正确修改密封类?忘记继承?使用成分?

但是现在,理解(我昨天没做过)所有事情sealed都在阻止继承,我们可以并且确实应该在继承上使用组合,我意识到我需要的是实际示例。

我想我的问题是(实际上一直都是)Mindor先生在聊天中向我提出的建议继承设计如何导致额外的成本?


5
这个问题的措词非常激进,听起来像是在咆哮。如果您想要相关和客观的答案,则应重新命名。
欣快的2013年

11
请参阅为什么密封了这么多框架类?埃里克·利珀特(Eric Lippert)。他提供了几个很好的理由来上课。
罗伯特·哈维

9
然后,您没有阅读该文章。返回并再次阅读。归结为:您无法预测客户通过扩展类破坏类的无数方法。 您可以为此责怪您的客户,但是您将是必须支持他们的人。
罗伯特·哈维

4
@Cawas他可以,或者您可以阅读他链接的文章,对其进行详细说明。他只是在评论中总结那篇文章。
Servy 2013年

7
抱歉,但我不是在这里抱怨的人。您的问题的答案非常简单:当您不想支持继承时,可以密封一个类。而已。
罗伯特·哈维

Answers:


27

这并不难理解。sealed是专门为Microsoft创建的,目的是使他们的生活更轻松,节省大量金钱并提高他们的声誉。由于它是一种语言功能,其他所有人也可以使用它,但是您可能永远都不需要使用密封的。

大多数人抱怨无法扩展课程,如果这样做,他们就会说每个人都知道,使其正常工作是开发人员的责任。没错,除非这些人对Windows的历史一无所知,而其中的一个问题sealed正在试图解决。

让我们假设开发人员扩展了.Net核心类(因为不存在密封),并使它完美地工作。是的,对于开发人员。开发人员将产品交付给客户。开发人员的应用程序效果很好。顾客很高兴。生活很好。

现在,Microsoft发布了一个新的操作系统,其中包括修复此特定.Net核心类中的错误。客户希望与时俱进,并选择安装新的操作系统。突然之间,客户非常喜欢的应用程序不再起作用,因为它没有考虑.Net核心类中已修复的错误。

谁来负责?

那些熟悉微软历史的人都知道,微软的新操作系统将受到指责,而不是滥用Windows库的应用软件。因此,Microsoft有责任解决问题,而不是由引起问题的应用程序公司解决。这是Windows代码肿的原因之一。根据我的阅读,Windows操作系统代码中散布着特定的if-,然后检查是否正在运行特定的应用程序和版本,如果正在运行,则进行一些特殊处理以允许程序运行。微软花了很多钱来解决另一家公司的无能。

使用sealed并不能完全消除上述情况,但确实有帮助。


4
@Cawas很难滥用它。埃里克·利珀特(Eric Lippert)曾多次表示,他希望类(如方法)在sealed默认情况下都是默认的,除非特别启封,原因是实际上实际上设计了很少的类来继承。您的班级很可能不是为支持它而构建的,并且如果您不打算将其标记为未密封,则应确保已花费了开发时间。
Servy 2013年

1
我认为,如果人们在内部开发软件中使用密封件,那么他们可能会滥用它,或者只是在浪费公司时间,以致于过于完美。我说这可能是因为在某些特定情况下,总有一些案例可以解决这个问题。但是,对于那些正在开发库类型软件的人,我怀疑它非常有用,原因与Microsoft认为有必要的原因相同。
Dunk

2
@Cawas 大多数开发人员编写的绝大多数类将永远不需要继承,也不需要编写来支持继承。通过创建类型,可以快速有效地将其传达给代码的阅读者/用户sealed。如果实际上是默认选项,则意味着如果有人从某种类型继承,那么他们会知道它是为支持它而构建的。
Servy 2013年

2
打开一个类并不意味着该类旨在支持它。这只是意味着有人想从该类继承并启封它。在最好的情况下,使用密封只会使开发人员重写现有功能(等同于密封类),但更多情况下,如果源代码可用,他们会简单地将其密封而不考虑任何后果。专门设置密封并且很少设置密封会更好,因为这样开发人员可能会想出为什么密封此类的原因。密封类太多,他们不在乎为什么。
Dunk

1
另外安全是一个理由去使用它!考虑一个试图访问文件的沙盒应用程序。沙箱可能会测试文件名字符串,但是如果攻击者可以向您发送一个可变文件 String,然后他们在安全检查之后但在I / O之前对它进行突变,该怎么办?我认为这种情况是为什么String标记Java final(类似于sealed)的原因,我敢打赌,相同的逻辑是某些C#决策的基础。
达里安

23

sealed用于表示该类的编写者尚未将其设计为可继承的。 什么都没有,仅此而已。

对于许多程序员来说,正确设计接口已经非常困难。正确设计可以潜在继承的类会增加难度和代码行。编写更多行代码意味着需要维护更多代码。为了避免增加这种困难,sealed可以表明该类从未打算被继承,在这种情况下,密封一个类是解决问题的完美有效方法

想象一下该类CustomBoeing787是从派生的Airliner。这CustomBoeing787会覆盖中的一些受保护的方法Airliner,但不是全部。如果开发人员有足够的时间并且值得,那么他可以对类进行单元测试,以确保一切正常,即使在调用非重写方法的上下文中(例如,更改对象状态的受保护的setter方法)。如果同一个开发人员(1)没有时间,(2)不想花费额外的成百上千美元的老板/客户,或者(3)不想编写其他他不愿意的代码现在需要(YAGNI),那么他可以:

  • 要么是按原样在野外发布代码,考虑到程序员的关心,他们将在以后维护该项目以关心潜在的错误,

  • 或将该类标记为sealed向未来的程序员明确表明该类不是要继承的,将其拆封并将其用作父级应自担风险。

密封尽可能多的类,还是尽可能少的类是一个好主意吗?根据我的经验,没有确切的答案。一些开发人员倾向于使用sealed过多的资源。其他人则不在乎该类将如何继承以及它可能引起的问题。考虑到这种用法,问题类似于确定程序员是否应该使用两个或四个空格进行缩进的问题:没有正确的答案。


好吧...很抱歉,如果我再次表现出攻击性,但是我只想在这里保持清晰和自信...如果您在tl;dr此处写一个,我只能理解您用两种方式中的任何一种编写的内容。要么是“没有,没有单一的正当理由”,要么是“我认为没有正当的理由,但我也不知道。” 。对我来说,“没有明确的答案”就是其中之一。
cregox

6
sealed用于表示该类的编写者尚未将其设计为可继承的。那是您唯一的好理由。
罗伯特·哈维

这是一个很好的理由,因为给您一辆没有灯光的自行车并强制执行,以某种方式,您无法添加灯光。
cregox

2
@Cawas怎么样?在这种情况下,对于自行车制造商来说,确保自行车没有灯光并不重要,但是对于那种类型的用户而言,确保实现是一种特定的精确实现通常重要。您可以执行所需的约束。在某些情况下,人们可能会添加实际上并不需要的约束。您提供了一个示例。仅仅因为约束被滥用在一个地方并不意味着您永远都不应约束任何东西。
Servy 2013年

1
另外安全是一个理由去使用它!考虑一个试图访问文件的沙盒应用程序。沙箱可能会测试文件名字符串,但是如果攻击者可以向您发送一个可变文件 String,然后他们在安全检查之后但在I / O之前对它进行突变,该怎么办?我认为这种情况是为什么String标记Java final(类似于sealed)的原因,我敢打赌,相同的逻辑是某些C#决策的基础。
达里安

4

当一个类被密封时,它允许使用该类型的代码确切地知道他们正在处理什么。

现在在C#stringsealed,您不能继承它,但是让我们假设情况并非如此。如果这样做,那么有人可以编写这样的类:

public class EvilString// : String
{
    public override string ToString()
    {
        throw new Exception("I'm mean");
    }
    public override bool Equals(object obj)
    {
        return false;
    }
    public override int GetHashCode()
    {
        return 0;
    }
}

现在这将是一个字符串类,它违反了设计人员string知道是正确的各种属性。他们知道该Equals方法将是可传递的,不是。哎呀,这个equals方法甚至不是对称的,因为一个字符串可以等于它,但不等于那个字符串。我敢肯定,各种各样的程序员都是在假设ToString方法string不会为非null值引发异常的情况下编写代码的。您现在可以违反该假设。

我们可以为它做很多其他的类,还有我们可能违反的各种其他假设。如果您删除以下语言的功能sealed然后,语言设计人员现在需要在他们的所有库代码中开始考虑类似的事情。他们需要执行各种检查,以确保他们希望使用的类型的扩展类型将被“正确地”写入,这可能是一件非常昂贵的事情(无论是在开发时还是在运行时)。通过能够密封一个类,他们可以确保这是不可能的,并且他们对自己的类型的实现细节所做的任何断言都不会受到侵犯,除了那些有意扩展的类型。对于那些旨在进行扩展的类型,您会发现语言代码在如何处理它们方面需要更具防御性。


但是您可以在代码中违反该假设。这不像您要获取String的源代码并对其进行编辑。设计人员不需要考虑这一点,因为它是1个叶子,1个分支,1个客户,自行承担风险和酌情权。它不会从那里传播,除非其他客户喜欢这样做。等等。
cregox

3
@Cawas如果在意外的时间引发异常并且结果导致资源泄漏,或者类处于不确定状态并无法正常运行?这种情况有点愚蠢,但是很容易有人写了一个他们认为明智的实现,但是偶尔会在不应该执行或无法使用某些值的情况下抛出该实现。然后,当语言代码不起作用时,他们会抱怨。最好不要让人们做该语言无法支持的事情。
Servy 2013年

无论如何,您都在谈论构建编译器。内置有许多此类sealed功能供我们使用。它仍然没有解释为什么在如此狭窄的范围之外使用它,并且像编译器代码一样狭窄,甚至没有解释sealed存在。
cregox

2
@Cawas string类型不是编译器代码。它是语言代码的一部分。完全不同。无论如何,我只是举了一个例子。您可以选择整个BCL中的所有密封类,并以它为例。
Servy 2013年

4
@Cawas:我认为您可能缺少的是客户支持方面。如果您允许一个类是可继承的,则客户将要从该类继承,然后在类中断时,他们将呼叫您。您可以整日告诉他们他们从该类继承自担风险,但是您仍然会接到电话,因为您允许他们从该类继承,因此他们认为这样做是安全的。
罗伯特·哈维

3

有没有使用密封的充分理由?

恩 不经常。仅当我确实有一个 确实要保持不变的不可变类型(或其他一些限制)并且不信任继承者来满足该要求而不将细微的催化错误引入系统时,才使用密封。

假设有充分的理由使用密封,并且我们应该将其用作所有内容的默认值,我们如何处理继承?

对于那些非默认的东西,我们使用继承。即使相对于继承(并且确实)更倾向于使用组合,这也不意味着继承将被丢弃。这意味着继承存在一些固有的问题,将其引入到设计中,代码的可维护性和组合在很大程度上减少了(通常)问题。这意味着,即使组成青睐的是传承有利于问题的某些子集。

我并不是说刻薄,但是如果您刚刚听说过SOLID和单元测试,也许您应该摆脱困境,然后编写代码。事情不是很明确,很少总是“总是X”或“永远不要使用Y”或“ Z最好”才是正确的方法(就像您似乎根据问题的观点而趋向于正确)。在编写代码时,您会看到sealed可能会很好的情况以及可能导致悲伤的情况- 就像其他折衷方案一样。


对我来说,这和Robert不想详细说明的“避免支持对客户的继承”是最有希望的答案……也许您可以详细说明那些确实使用过的情况sealed,请吗?
cregox

@Cawas-我不确定我会怎样。我很少这样做,这通常是一个折衷,在这种情况下,保证某些东西保持不变(以进行性能优化,简化设计)值得不从该对象继承。
Telastyn

这很酷。扣篮已经扣篮了!:P非常感谢您的回答。而且,您是唯一了解我的专业知识的“微妙提示”的人。似乎人们以为我知道更多,我不知道...但是我希望“仅仅编码”会让我从这块岩石中走出来。也许我确实以某种方式应用了这些概念,只是在程序上不尽如人意。
cregox

3

首先,您应该阅读埃里克·利珀特(Eric Lippert)的文章,其中介绍了Microsoft为什么要密封他们的课程。

http://blogs.msdn.com/b/ericlippert/archive/2004/01/22/61803.aspx

其次,密封关键字的存在是为了准确地执行它的工作-防止继承。在许多情况下,您想防止继承。考虑一个您有一个集合的类。如果您的代码依赖于该类以特定方式工作,则您不希望有人重写该类中的方法或属性之一并导致您的应用程序失败。

第三,使用密封会鼓励组合而不是继承,这是一种很好的习惯。在继承上使用合成意味着您无法违反Liskov的替换原则,并且遵循开放/封闭原则。 sealed因此,它是一个关键字,可帮助您遵循oo编程的五个最重要原则中的两个。我想说这使其成为非常有价值的功能。


问题的注释中已经指出了第一部分。第二部分只是没有实际因果关系的更多推理。第三部分,即使已经在其他地方使用,也可能有所帮助。我会专注于那个。
cregox

第三点不鼓励在继承上进行组合,即使完全是最佳选择,它也完全消除了继承的选择。
Michael Shaw

这取决于您是否控制代码库。如果您密封班级,则随时可以在需要时将其密封。
斯蒂芬·

2

我认为这个问题没有客观答案,这完全取决于您的观点。

一方面,有些人希望一切“开放”,确保一切正常的责任在于扩展班级的人。这就是默认情况下类开放继承的原因。以及为什么Java方法在默认情况下都是虚拟的。

另一方面,有人希望默认情况下关闭所有内容并提供打开它的选项。在这种情况下,基类的作者需要确保以任何可以想象的方式安全地使用他进行的“开放”操作。这就是为什么在C#中默认情况下所有方法都是非虚拟的,并且需要声明为虚拟的才能被覆盖。对于那些人来说,也应该成为规避“开放”违约的方式。就像使班级封闭/最终化一样,因为他们看到某个人从班级中衍生出来,成为他们自己班级设计的一个大问题。


这是更喜欢它!这可能是一个很好的理由……“因为人们想关闭它”。这也是我要在这个问题中说的:似乎可以重写C ++中的非虚拟类。sealed不能。我们如何从他们那里继承?我所读的是我们做不到。曾经 我不知道。自从我第一次听说密封以来,我已经在这个主题上来回了4个月了。当我需要修改密封类上的任何内容时,我仍然不知道如何正确地做。因此,我看不到“打开它的选项”!
cregox

4
这不是为什么C ++类方法默认情况下不是虚拟的原因。将方法虚拟化意味着方法查找表上必须有额外的开销,并且C ++的理念是仅在需要功能时才需要开销。
迈克尔·肖

@托勒密好吧,我将其删除。我不应该写它,因为我知道人们开始抱怨它。
欣快的2013年

嘿,我完全感谢您,如果您想声称人们默认情况下会写封闭的课程,并且必须主动选择打开它们,那么您将不得不抓紧稻草
Michael Shaw

IMO与API的私有/受保护/公共辩论极为相似:它在很大程度上取决于谁控制该类,谁想继承该类,两者之间的通信以及新版本的发布频率。
达里安

-2

C#中密封的问题在于它相当最终。在7年的C#商业开发中,我唯一看到的地方是在Microsoft .NET类上,我觉得我有正当的理由扩展框架类以实现调整的行为,并且由于该类是密封的,所以我无法。

不可能编写一个类来考虑使用它的所有可能方式,并确定它们都不是合法的。同样,如果框架安全性取决于不继承的类,那么您就有安全性设计缺陷。

因此,总而言之,我坚决认为密封是没有用的目的,到目前为止,我在这里所读到的内容都没有改变任何观点。

我可以看到,到目前为止,有三个论点证明密封是合理的。

  1. 论点1是,当人们从类继承并在不真正了解内部原理的情况下增加了对该类内部的访问权限时,它消除了bug来源。

  2. 论点2是,如果您扩展了Microsoft类,然后Microsoft在该类中实现了错误修复,但是您的扩展依赖于该错误,则您的代码现在被破坏了,Microsoft会对此负责,并且密封可以防止该错误

  3. 论据3是作为类的开发者,作为我设计类的一部分,我是否选择允许您对其进行扩展是我的选择。

参数1似乎是您的最终程序员不知道如何使用我的代码的参数。可能是这种情况,但是如果您仍然允许我访问对象接口,那么我仍然在使用您的对象,并且在不了解如何使用它的情况下对其进行调用,这同样可能会造成这种损害。这是需要密封的微不足道的理由。

我知道arg 2的事实依据来自何处。特别是当Microsoft对win32 api调用进行额外的OS检查以识别格式错误的调用时,与Windows NT相比,这改善了Windows XP的总体稳定性,但是在短期内,发行Windows XP时许多应用程序都被破坏了。微软通过在“规则”中进行这些检查来解决此问题,例如说当应用X违反此规则时忽略它,这是一个已知的错误

论点2是一个糟糕的论点,因为密封最多几乎没有什么区别,因为如果.NET库中存在错误,您别无选择,只能找到解决方法,并且当Microsoft修补程序发布时,很有可能意味着您依赖于坏行为的工作现在已经坏了。无论您是通过使用继承还是通过其他一些包装程序代码来解决此问题,当代码固定后,您的解决方案都会被破坏。

论点3是唯一具有任何能证明其自身合理性的论点。但是它仍然很弱。它只是与代码重用背道而驰。


2
/ me向Microsoft发送电子邮件,请您为我打开X类并发布新版本的.NET。亲切的问候,....就像我说的那样,在商业发展中,我唯一一次见过密封班,就不可能解开它。
迈克尔·肖

6
It is impossible to write a class thinking about every possible way it could be used-这正是许多Framework类被密封的原因...它消除了思考他人扩展类的所有可能方式的需要。
罗伯特·哈维

5
如果您可以将其打开,则将其密封没有太大意义,对吗?
罗伯特·哈维

2
@Ptolemy允许人们扩展您的课程是一项功能。一种需要大量开发工作的软件。只有在班级作者是否可以在潜在的扩展中看到足够的好处来证明所做的努力时,才值得证明这一功能。如果他们不能证明工作的合理性,并且因此类型和它的用户不是为支持扩展而构建的,那么就必须加以防止,否则从技术上讲,您可以扩展该类型,但是当它不能正常工作时,你这样做。
Servy

2
@卡瓦斯:你只是很难。
罗伯特·哈维


-8

从我的问题可以明显看出,我不是专家。但是我认为没有理由sealed存在。

似乎是为了帮助代码架构师设计接口。如果您想编写一个会狂奔的类,并且有很多人会继承它,那么修改该类中的任何内容都会使太多人无法使用。因此我们可以密封它,没有人可以继承。“问题解决了”。

好吧,看起来就像“发现一个地方然后发现另一个地方”。而且它甚至都没有解决问题-只是消除了一种工具继承。作为任何工具,它都有许多用途,并且从不稳定类继承会带来风险。冒险的人应该更了解。

也许会有一个关键字stable,所以人们会知道从它继承是更安全的。


2
“问题解决了”-确实如此。
罗伯特·哈维

@RobertHarvey尚未解决,如下一段所述。
cregox

8
您只是生气,因为您无法扩展课程。这并不意味着sealed它对框架设计者不是有用的工具。框架中的类应为黑匣子;您不必了解类的内部结构就能正确使用它。但这正是继承的要求。
罗伯特·哈维

我从未说过它没有用。我说我看不到它的用法,并指出原因。拜托,我求你了,给我一个使用它的充分理由!即使只是“因为我想遵循封闭的设计模式”,也没关系。我没有看到任何人明确给出这个理由。
cregox

7
一个很好的理由是,您不必在密封类上支持继承。
罗伯特·哈维
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.