重写的阴影






4.88/5 (10投票s)
本文希望能够帮助您缩短学习曲线。
引言
作为一名新的 VB 开发人员,我不确定您是否遇到过利用 VB.NET 实现的一些基本原理的应用程序。具体来说,是 `Shadows` 和 `Overrides` 关键字之间的区别。本文希望能够帮助您缩短学习曲线。
背景
在 .NET 热潮之前,商界非常青睐 Visual Basic,而在更学术的圈子里则青睐 C++。这两种语言之间有趣的核心区别在于塑造其开发的基本设计原则。C++ 的指导原则是“保持运行时精简,并假设键盘后面是一位天才开发者”。Visual Basic 的核心是“快速开发,假设普通开发者”。到此为止,我已经开始了永无止境的 VB 与 C 的争论。请稍等片刻,再向我施放火球术。这些语言的设计目标是不同的,并非不好——只是不同。由于这些设计原则,VB 拥有相当多的运行时功能;因此,字符串在 VB 中是原生的,并且不鼓励使用指针。相比之下,在 C++ 中,字符串是外部的,而指针是一等公民对象。结果是,旧版 VB 并不像 C++ 那样支持许多面向对象原则,特别是:继承和多线程(Visual Basic 2007)。VB.NET 已经弥补了这一不足,因此我们有了 C# 和 VB.NET 的宗教战争。
基础
…
Public Class Invoice
Public Sub ProcessInvoice()
…
End Sub
End Class
Public Class TimeMaterials
Inherits Invoice
Public Overloads Sub ProcessInvoice(Invoice as Int)
…
End Sub
End Class
…
VB.NET 凭借其新功能需要新的术语。然而,在我们讨论解决方案之前,我们应该了解它们试图解决什么挑战。假设我有一个对象 `Invoice`,它有一个 `ProcessInvoice` 函数。此外,假设我想从 `Invoice` 派生出一个子类 `TimeMaterialsInvoice`,它也有一个 `ProcessInvoice` 函数,请参见代码片段 1。在代码的其他地方,我有一个如下的代码片段:
…
Dim bill as Invoice = new TimeMaterialsInvoice()
Bill.ProcessInvoice()
…
问题
作为一个聪明年轻的编译器,我该怎么办?当然,我会扩展 `TimeMaterialsInvoice` 对象的账单来模拟 `Invoice` 类。但是,我知道 `ProcessInvoice` 有两个实现,而且我知道这段代码有能力访问它们两者。所以作为一个计算机,这种歧义让我感到困惑;因此,我必须向用户吐出错误!用户必须解决我的问题。
解决方案
最终,只有少数几种解决方案:用户希望调用基类方法,用户希望有权调用任一函数,或者用户希望调用已实例化的版本。首先,如果用户希望调用 `Invoice` 函数,那么 `TimeMaterialsInvoice` 代码应该使用 `Shadows` 关键字。这样,`TimeMaterialsInvoice` 代码就不会被调用。一种替代方法是,尽管会产生警告,但要删除两个函数的所有修饰符,因为 VB.NET 编译器假定 `Shadows` 关键字适用于 `TimeMaterialsInvoice` 方法。
如果消费者有时想调用 `Invoice` 函数,有时想调用 `TimeMaterialsInvoice` 方法,这可以通过函数签名来实现;换句话说,基于最佳参数匹配。编译器会做出有根据的猜测来选择正确的实现。不幸的是,由于 `ProcessInvoice` 的两个版本具有相同的签名,在这种情况下,这不是一个选项(参见附录)。
如果消费者希望调用 `TimeMaterialsInvoice` 方法,那么客户端代码或基类必须知道子类。假设客户端代码知道子类定义,那么调用子类 `ProcessInvoice()` 的解决方案很简单;将对象的定义更改为以下内容:
Dim bill as TimeMaterialsInvoice = new TimeMaterialsInvoice()
然而,这可能不是理想的,因为它强制客户端使用单一的 `TimeMaterialsInvoice` 实现。通常的最佳实践是根据定义了所需响应的最通用接口或基类进行编码。因此,客户端代码应该如下所示:
Dim bill as Invoice = new TimeMaterialsInvoice()
注意:我们对象的更通用定义。为了使用此语法,并产生调用 `ProcessInvoice` 的 `TimeMaterialsInvoice` 版本的效果,我们需要添加 `Overridable` 和 `Overrides` 关键字。
…
Public Class Invoice
Public Overridable Sub ProcessInvoice()
…
End Sub
End Class
Public Class TimeMaterials
Inherits Invoice
Public Overrides Sub ProcessInvoice(Invoice as Int)
…
End Sub
End Class
基类中的 `Overridable` 关键字允许编译器用子实现替换所述函数。同样,`Overrides` 关键字通知编译器此函数将覆盖同名的原始函数。
概念上,`Shadows` 和 `Overrides` 之间没有区别,因为它们都改变了调用树;然而,它们在解决方案上是根本不同的。回顾我们的例子:
…
Dim bill as Invoice = new TimeMaterialsInvoice()
Bill.ProcessInvoice()
…
`Bill` 的类型是 `Invoice`,而其实例是 `TimeMaterialsInvoice`。通常,`Shadows` 假定调用与类型相关的函数,而 `Overrides` 假定执行对象实现。
附录 – 其他语言实现
讽刺的是,所有这些挑战都存在于 C#、Java 和任何其他 OOP 编程语言中。
VB.NET |
C# |
|
|
|
- |
|
|
|
|
注意:关于重载
`Overloads` 关键字,尽管在类设计时使用,但与 `Shadows` 或 `Overrides` 相比,可以产生非常不同的语法结果。正式地,重载是创建具有相同名称的多个定义;因此,为了区分它们,编译器要求它们具有不同的方法签名。VB.NET 编译器出于清晰度的原因,决定要求 `Overloads` 关键字(如代码片段 2 所示)来向类使用者公开两个实现。一个真实的例子是 `DateTime.ToString` 函数。另一个重载选项是 `DateTime.ToString(String)`,其中 `String` 参数是格式化选项。如果 `DateTime` 的使用者想要默认值,则选择第一个实现。相反,如果使用者希望明确其格式,他可以通过提交 `String` 参数来选择第二个。
…
Public Class Invoice
Public Sub ProcessInvoice()
…
End Sub
End Class
Public Class TimeMaterials
Inherits Invoice
Public Overloads Sub ProcessInvoice(Invoice as Int)
…
End Sub
End Class
…
修订日志
02-01-07
- 发布文章。
02-15-07
- 根据 Menski 先生的建议,修改了 C# 交叉引用。
- 使超链接生效。
参考文献
- Killian, G. (2003, August 23)。消化 Shadows vs. Overrides。检索自 2007 年 1 月 27 日,来自 Code Better:http://dotnetjunkies.com/WebLog/grant.killian/archive/2003/08/24/1225.aspx。
- 面向对象编程。(2007, January 29)。检索自 2007 年 1 月 29 日,来自 Wikipedia:http://en.wikipedia.org/wiki/Object-oriented_programming。
- Visual Basic. (2007, January 24)。检索自 2007 年 1 月 29 日,来自 Wikipedia:http://en.wikipedia.org/wiki/Visual_basic_6。
- Visual Basic 语言概念:Shadowing (n.d.)。检索自 2007 年 1 月 20 日,来自 MSDN Home:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcn7/html/vbconshadowing.asp。