同事们,我不完全理解.NET 设计指南中的建议之一。
它说:
确实更喜欢受保护的可访问性而不是虚拟成员的公共可访问性。公共成员应通过调用受保护的虚拟成员来提供可扩展性(如果需要)。
类的公共成员应该为该类的直接消费者提供正确的功能集。虚拟成员被设计为在子类中被覆盖,并且受保护的可访问性是将所有虚拟可扩展点限制在可以使用它们的位置的好方法。
那是
更喜欢使虚拟成员(如方法)受保护而不是公开。类的公共成员必须通过调用受保护的虚拟成员来提供可扩展性(如果需要)。
类的公共成员应为客户端代码提供所需的正确功能。虚拟成员被设计为在后代类中被覆盖,并且安全访问是一种很好的技术,可以限制场所的可见性以仅扩展到将使用它的人。
读完这篇文章后,我仍然不明白如果将虚函数族声明为 public 会出现什么问题。例如,在这段代码中:
class Human : IDisposable
{
IDisposable property = new Property();
public virtual Dispose()
{
property.Dispose();
}
}
class Spy : Human
{
IDisposable spyGadgets = new SpyGadgets();
public virtual Dispose()
{
base.Dispose();
spyGadgets.Dispose();
}
}
这样的代码可能有什么问题?试图警告我的文档是什么?如果这种情况一切正常,那么在什么情况下可能出现问题?
如果可能的话,给出一个有意义的代码示例(而不是类Foo和Bar)。
非常感谢您的回复!我很难选择打哪一个,因为所有的答案都很好,并且从不同的角度阐明了问题。
这些是对框架开发人员的建议。显然,框架开发人员将发布其框架的新版本。同样清楚的是,他们最重要的任务之一是尽可能保持向后兼容性。这意味着应尽可能限制使用其代码的客户端。也就是说,习惯于编写要继承的类的是我们(好吧,或者只有我是这样一个笨手笨脚的人)——但是如果我们正在编写一个框架,那么我们需要一个足够好的理由来使类被继承。您还需要一个充分的理由来使该方法成为虚拟方法。但是现在您将公共方法设为虚拟,现在客户可以继承、重载该方法并使用我们的框架运行他们自己的代码——我们无法再控制它了。我的意思是,通过使公共方法虚拟化,我们赋予客户决定权 我们的 API 将做什么,我们不能在不破坏向后兼容性的情况下改变任何东西。但是,通过将受保护的方法设为虚拟,我们不保证客户端将来如果我们的公共 API 发生更改,该方法不会变得不可用。因此,即使公共方法逻辑已更改,重载受保护方法的客户端仍保持向后兼容。
我想我需要添加一个示例,虽然我不是示例大师:) 假设有以下类:
现在让我们想象一下,在我们精彩框架的下一个版本中,我们需要在没有标头的 CSV 中编写对象。也就是说,不再需要标头。而且你不能再覆盖它了。该怎么办?上课
CsvWriter1很容易就班级而言,
CsvWriter2我们处于水坑中。由于对它的指定更改将被认为是不兼容的,当然它会破坏客户端类的逻辑。我在 2000 年代初在 Herb Sutter 遇到了非虚拟接口的想法,然后在我看来这是一个很棒的想法(这并不奇怪,我是用 C++ 编写的,这种做法非常流行原则上,甚至由行业名人描述)。
这是他的(萨特)的文章——虚拟性——对这个主题的解释(在我在上一段中提到的那个帖子中找到):
当我 10 年前迁移到 .NET 时,我尝试使用这个成语,只是出于习惯。但随后,他开始在她身上得分。
现在,我们可以分析这个建议。
结论:在充分尊重框架设计指南和 Sutter 个人的情况下,但这个建议可以而且应该被评分。为了创造方法而创造方法是浪费时间。我已经很长时间没有使用这个建议了,因此我不记得任何一个问题。是的,框架的作者没有遵循相同的建议,也没有任何特殊问题(因此)。
西南。@Denis Gladkiy 提出了这样的问题:
然后应该以更明确的方式回答:
我向框架设计指南的作者 Krzysztof Kwalina 提出了同样的问题——“他今天对这个建议有什么看法?”。这是他的答案。
公共方法是类的契约。如果公共方法被声明为虚拟的,则意味着子类可以很容易地以违反约定的方式覆盖它。
在受保护的方法的情况下,破坏合同并不是那么容易(有时是不可能的),因为主要逻辑是硬编码在具有扩展点的非虚拟公共方法中 - 那些相同的受保护虚拟方法。
有时这种技术也可以用来防止健忘的程序员,这样当重写一个方法时,他们“不会忘记”调用底层逻辑。像这样的东西。
所以是的,“更喜欢”。但它是首选。
Dispose方法没有问题:它的逻辑非常简单,它的合约是微软一劳永逸的,以后不会改变。
但通常在基类中有一些更复杂的算法,其扩展点在中间,而不是在边缘。这就是为什么我们需要一个单独的方法:
如果在上面的示例中它是声明为虚拟的公共 Validate 方法,那么任何派生类都必须复制基类的逻辑。
一个更简单的情况是防御性参数检查。在基类中检查一次参数就足够了 - 您不应该在每个类中编写相同的检查。
我经常使用这种方法,但说实话,我以前从未遇到过这个建议。原因很简单,在某个类层次结构的开发之初,并不总是清楚(相反,总是不清楚)哪些公共方法代码将被共享,哪些需要在继承者中重新定义。因此,作为起点,公共方法只是简单地调用受保护的虚拟方法。将来,受保护方法中的通用代码移动到公共方法中,而特定代码保留在受保护方法中。如果结果,所有代码都保留在受保护方法中,并且只保留对该方法的调用在公共方法中,然后我们将所有代码转移到公共方法并使其成为虚拟方法。
当然,您也可以采取其他方式——始终以单独的方法显示细节,但这通常需要我更多的关注和手动工作。
在使用虚拟公共方法时,我从未见过任何真正的问题,例如在公共字段而不是属性或常量而不是只读属性的情况下。
NVI 加 5 美分:NVI 和 C#
在一个简单易懂的示例中,无法在代码中显示不遵循这一点的问题。开发和维护代码的复杂性问题。
在我看来,这类似于理解备份的必要性
(例如,许多人已经习惯性地进行备份,但并不是每个人都在测试他们的备份)和许多其他类似的事情。