为什么静态方法不能被重写?


13

在回答这个问题时,普遍的共识是静态方法不应被覆盖(因此C#中的静态函数不能是虚拟的或抽象的)。但是,不仅在C#中是这种情况。Java也禁止这样做,C ++也似乎不喜欢它。但是,我可以想到许多我想在子类中覆盖的静态函数示例(例如,工厂方法)。从理论上讲,有多种方法可以解决这些问题,但都不是干净或简单的方法。

为什么静态函数不应该被覆盖?


4
Delphi支持方法,该方法与静态方法非常相似,并且可以被覆盖。它们传递了self指向该类而不是该类实例的指针。
CodesInChaos

静态的一个定义是:缺乏变化。我很好奇为什么要将要覆盖的函数设为静态。
JeffO'9

4
@JeffO:“静态”的英文定义与静态成员函数的独特语义完全无关。
Lightness Races in Orbit

5
您的问题是“为什么静态方法应该使用静态类型的调度而不是单虚拟调度?” 当您以这种方式表达问题时,我认为它可以回答自己。
埃里克·利珀特

1
@EricLippert最好将问题表述为“为什么C#提供静态方法而不是类方法?”,但这仍然是一个有效的问题。
CodesInChaos

Answers:


13

对于静态方法,没有对象提供对替代机制的适当控制。

普通的类/实例虚拟方法机制允许对覆盖进行微调控制,如下所示:每个实际对象都是一个类的实例。该类确定替代的行为。在虚拟方法中,它始终是第一个漏洞。然后,它可以选择在正确的时间调用父方法进行实现。然后,每个父方法都可以轮流调用其父方法。这导致父级调用很好地级联,从而实现了面向对象的代码重用概念之一。(在这里,基类/超级类的代码以相对复杂的方式被重用; OOP中代码重用的另一个正交概念是简单地具有相同类的多个对象。)

基类可以被各种子类重用,并且每个子类都可以舒适地共存。用于实例化对象的每个类指示自己的行为,并与其他对象和平并存。客户端可以通过选择要用于实例化对象的类并根据需要传递给其他对象来控制其所需的行为以及何时进行控制。

(这不是一种完美的机制,因为人们总是可以识别出不支持的功能,这就是为什么工厂方法和依赖项注入之类的模式位于最顶层的原因。)

因此,如果我们要在不更改其他任何特征的情况下为静态变量提供覆盖功能,则将难以订购覆盖。很难为覆盖的适用性定义一个有限的上下文,因此您将全局获取覆盖,而不是像对象那样在本地获取覆盖。没有实例对象可以切换行为。因此,如果有人调用了恰好被另一个类重写的静态方法,则该重写是否应获得控制?如果存在多个此类替代,谁先控制了谁?第二?使用实例对象覆盖,这些问题都有有意义且合理的答案,而使用静态对象则没有。

覆盖静态变量会非常混乱,而且以前已经做过类似的事情。

例如,Mac OS System 7及更高版本使用陷阱修补程序机制,通过在操作系统之前控制对应用程序进行的系统调用来扩展系统。您可以将系统调用补丁程序表视为函数指针数组,非常类似于实例对象的vtable,只是它是单个全局表。

由于陷阱修补程序的无序性质,这给程序员带来了不尽的悲伤。最后一个打补丁的人基本上会赢,即使他们不想打。陷阱的每个修补程序都将捕获先前的陷阱值,以用于某种父调用功能,这是非常脆弱的。删除陷阱补丁程序,比如说当您不再需要了解系统调用时,就被认为是不好的形式,因为您实际上没有删除补丁程序所需的信息(如果这样做了,您还将取消对随后的其他任何补丁程序的补丁程序您)。

这并不是说不可能创建覆盖静态的机制,而是我可能更愿意做的是将静态字段和静态方法转换为元类的实例字段和实例方法,以便普通对象然后将应用定向技术。请注意,有些系统也可以这样做:CSE 341:Smalltalk类和元类;另请参阅:与Java静态等效的Smalltalk是什么?


我想说的是,您必须进行一些认真的语言功能设计才能使其在合理范围内正常工作。举一个例子,一个幼稚的方法确实做到了,并一无所获,但是却是一个很大的问题,而且可以说(即我认为)通过提供不完整且难以使用的抽象而在架构上存在缺陷。

当您完成静态替代功能的设计以使其正常工作时,您可能刚刚发明了某种形式的元类,这是OOP对基于类的方法的自然扩展。因此,没有理由不这样做-有些语言实际上是这样做的。许多语言选择不这样做可能只是附带要求。


如果我的理解正确:从理论上讲,您是否愿意做这样的事都不是问题,而从编程的角度出发,要实现它很困难并且不一定有用?
PixelArtDragon

@Garan,请参阅我的附录。
埃里克·艾德

1
我不接受订购的论点。您将获得由您在其上调用该方法的类提供的方法(或第一个根据您的语言选择的线性化提供方法的超类)。
hobbs

1
@PierreArlaud:但是this.instanceMethod()具有动态分辨率,因此,如果self.staticMethod()具有相同的分辨率(即动态),那么它将不再是静态方法。
约尔格W¯¯米塔格

1
需要添加静态方法的覆盖语义。假设的Java +元类不需要添加任何内容!您只需创建类对象,然后静态方法便成为常规实例方法。您不必在语言中添加任何内容,因为您只需要使用已经存在的概念即可:对象,类和实例方法。另外,您实际上可以从语言中删除静态方法!您可以删除语言中的概念,从而消除复杂性,从而添加功能!
约尔格W¯¯米塔格

9

覆盖取决于虚拟调度:您可以使用this参数的运行时类型来决定要调用的方法。静态方法没有this参数,因此没有派生对象。

某些语言(尤其是Delphi和Python)具有“介于两者之间”的范围,可以实现以下目的:类方法。 类方法不是普通的实例方法,但也不是静态的。它接收一个self参数(有趣的是,两种语言都调用thisparameter self),该参数是对对象类型本身的引用,而不是对该类型实例的引用。使用该值,现在您可以使用类型进行虚拟调度。

不幸的是,JVM和CLR都没有可比的东西。


是使用self具有可重写方法的类作为实际实例化对象的类的语言-当然是Smalltalk,Ruby,Dart,Objective C,Self,Python?与C ++之后的语言相比,即使有一些反射访问,类也不是一流的对象?
Jerry101'9

为了清楚起见,您是说在Python或Delphi中,您可以覆盖类方法吗?
埃里克·艾德

@ErikEidt在Python中,几乎所有东西都是可重写的。在Delphi中,可以virtual像实例方法一样,显式声明一个类方法,然后在后代类中对其进行重写。
梅森惠勒

因此,这些语言提供了某种元类的概念,不是吗?
埃里克·艾德

1
@ErikEidt Python具有“ true”元类。在Delphi中,类引用是一种特殊的数据类型,而不是类本身。
梅森惠勒

3

你问

为什么静态函数不应该被覆盖?

我问

为什么要覆盖的函数应该是静态的?

某些语言会强制您以静态方法开始播放。但是之后,您真的可以解决很多问题,而无需使用任何其他静态方法。

有些人喜欢在不依赖对象状态的任何时候使用静态方法。有些人喜欢使用静态方法来构造其他对象。有些人喜欢尽可能避免使用静态方法。

这些人没有错。

如果您需要使其可重写,则停止将其标记为静态。一切都不会中断,因为您有一个无状态的物体飞来飞去。


如果语言支持结构,则单位类型可以是大小为零的结构,并将其传递给逻辑输入方法。该对象的创建是一个实现细节。而且,如果我们开始将实现细节作为实际事实来讨论,那么我认为您还需要考虑在实际实现中,不必实际实例化零大小的类型(因为不需要加载任何指令)或存储)。
Theodoros Chatzigiannakis

而且,如果您在“幕后”(例如在可执行程序级别)进行更多讨论,则可以将环境参数定义为语言中的类型,并且程序的“真实”入口点可以是这种类型,作用于OS加载程序传递给程序的任何实例。
Theodoros Chatzigiannakis

我认为这取决于您如何看待它。例如,当程序启动时,操作系统将其加载到内存中,然后转到该程序的二进制入口点的单独实现所驻留的地址(例如_start()Linux上的符号)。在该示例中,可执行文件是对象(它具有自己的“调度表”和所有内容),入口点是动态调度的操作。因此,从外部查看时,程序的入口点本质上是虚拟的,从内部查看时,也很容易使它看起来像虚拟的。
Theodoros Chatzigiannakis's

1
“一切都不会中断,因为您有一个无状态的物体飞来飞去。” ++++++
RubberDuck

@TheodorosChatzigiannakis,您可以提出强有力的论据。如果您没有说服我,那么这条线并不会激发我的意图。我真的是在试图让人们摆脱一些盲目的与静态方法相关联的习惯性行为。所以我已经更新了。有什么想法吗?
candied_orange

2

为什么静态方法不能被重写?

这不是“应该”的问题。

“覆盖”是指“ 动态分配”。“静态方法”是指“静态分派”。如果某些内容是静态的,则不能覆盖它。如果可以覆盖某些内容,则它不是静态的。

您的问题有点像问:“为什么三轮车不能有四个轮子?” “三轮车” 的定义是具有三个轮子。如果是三轮车,则不能有四个轮子,如果是四个轮,则不能是三轮车。同样,“静态方法” 的定义是静态分配的。如果它是静态方法,则不能动态调度,如果可以动态调度,则不能是静态方法。

当然,完全有可能会覆盖类方法。或者,您可以使用Ruby之类的语言,其中类是与其他任何对象一样的对象,因此可以具有实例方法,从而完全消除了对类方法的需要。(Ruby只有一种方法:实例方法。它没有类方法,静态方法,构造函数,函数或过程。)


1
“按定义”好说。
candied_orange

1

因此,C#中的静态函数不能是虚拟的或抽象的

在C#中,您始终使用类来调用静态成员,例如BaseClass.StaticMethod()not baseObject.StaticMethod()。因此,最终,如果您ChildClass继承自BaseClasschildObject的实例ChildClass,则将无法从调用静态方法childObject。您将始终需要显式使用真实的类,因此static virtual公正是没有道理的。

您可以做的是static在子类中重新定义相同的方法,并使用new关键字。

class BaseClass {
    public static int StaticMethod() { return 1; }
}

class ChildClass {
    public static new int StaticMethod() { return BaseClass.StaticMethod() + 2; }
}

int result;    
var baseObj = new BaseClass();
var childObj = new ChildClass();

result = BaseClass.StaticMethod();
result = baseObj.StaticMethod(); // DOES NOT COMPILE

result = ChildClass.StaticMethod();
result = childObj.StaticMethod(); // DOES NOT COMPILE

如果可以致电baseObject.StaticMethod(),那么您的问题就很有意义了。

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.