我不能只使用所有静态方法吗?


64

下面的两个UpdateSubject方法有什么区别?我觉得如果只想对实体进行操作,使用静态方法会更好。在什么情况下应该使用非静态方法?

public class Subject
{
    public int Id {get; set;}
    public string Name { get; set; }

    public static bool UpdateSubject(Subject subject)
    {
        //Do something and return result
        return true;
    }
    public bool UpdateSubject()
    {
        //Do something on 'this' and return result
        return true;
    }
}

我知道,对于这个非常烦人的问题,我会从社区中得到很多帮助,但我无法阻止自己提出这个问题。

在处理继承时,这变得不切实际吗?

更新:
它正在我们的工作场所中发生。我们正在与5位开发人员一起开发6个月的asp.net Web应用程序。我们的架构师决定我们对所有API使用所有静态方法。他认为静态方法的理由是重量轻,并且可以通过减少服务器负载来使Web应用程序受益。


9
Rich Hickey会鼓励类似的习惯用法(所有静态方法)。如果您喜欢一种功能样式,则不妨使用一种功能语言;)并非软件中的所有内容都最好建模为对象。
工作

13
@Job:我不太了解它的功能-它是程序性的。
亚伦诺特,2011年

2
@工作:此类不是一成不变的。
2011年

3
@DonalFellows:当然可以,C#和F#都是混合范例。但是事实仍然存在,静态方法==函数编程。FP表示传递/链接函数,不可变的数据类型,避免副作用等。这与OP中的代码段完全相反。
亚伦诺特,2012年

4
“他的推理是静态方法,重量轻,并且通过减少服务器负载使Web应用程序受益”。那是错的。保持服务器负载低?
mamcx 2013年

Answers:


86

我将处理带有显式“ this”参数的静态方法最明显的问题:

  1. 您将失去虚拟调度,随后失去多态性。您永远不能在派生类中重写该方法。当然,您可以在派生类中声明一个newstatic)方法,但是访问该类的任何代码都必须了解整个类的层次结构,并进行显式检查和强制转换,这正是OO应该避免的

  2. 作为#1的扩展,您不能用interface代替类的实例,因为接口(在大多数语言中)不能声明static方法。

  3. 不必要的冗长。哪个更易读:Subject.Update(subject)或者仅仅是subject.Update()

  4. 参数检查。再次取决于语言,但是许多人将编译隐式检查以确保该this参数不是null为了防止空引用错误创建不安全的运行时条件(缓冲区溢出之类)。如果不使用实例方法,则必须在每个方法的开头显式添加此检查。

  5. 令人困惑。当一个正常的,合理的程序员看到一个static方法,他自然会认为它并不需要一个有效的实例(除非它需要多个实例,如比较或平等法,或者预计能够在操作null参考) 。看到以这种方式使用的静态方法会让我们做两次或三次尝试,在第四次或第五次之后,我们会感到压力和愤怒,如果我们知道您的家庭住址,上帝会帮助您。

  6. 这是一种重复形式。调用实例方法时实际发生的情况是,编译器或运行时在类型的方法表中查找该方法并将其this用作参数来调用。您基本上是在重新实现编译器已经完成的工作。您违反了DRY,不需要时以不同的方法反复重复相同的参数。

很难想到有充分的理由将实例方法替换为静态方法。请不要。


5
+1为您的最后一行。我希望更多的人在考虑“静态方法”之前先考虑“实例方法”。
Stuart Leyland-Cole 2012年

4
+1。我要补充一点,因为静态方法很难编写测试,因为在大多数情况下,您无法模拟它们:一旦代码的任何部分都依赖于静态方法,就不能用no-代替它。在测试中进行操作。.NET世界中的一个主要示例是FileFileInfo和这样的类DirectoryInfo(实际上,有些库将它们隐藏在接口后面,然后可以根据需要将其注入并在测试中进行模拟)。
sm 2014年

我认为您对多态性/继承性的观点尚无定论。一个人不应该将继承用于代码重用,所以这是无关紧要的。继承部分也令人怀疑。在大多数现代语言中,方法签名本身就是多态的。如果您采用功能性方法,则将开始遍历方法,并根据可基于其接口互换的简单方法组成复杂的方法。静态方法没有缺点,就像通过OOP一样,只要记住这些静态方法就不能弥补这些缺点。
萨拉

@sm根本不是真的。如果您对任何静态或非静态的东西都具有严格的依赖关系,那就不能在单元测试中进行,那是不可测的时期。静态不会导致这种情况。可以使用与表示db连接的类相同的方式注入静态方法。您假设“静态”是指“静态以及涉及全局状态和外部资源的硬隐藏依赖项”。这不公平。
萨拉

@kai尝试注入执行Y而不是X的静态方法,无论X和Y是什么。您不需要依赖于任何外部因素。也许您只是对测试不需要执行冗长的计算X的某些方面感兴趣。那么如何在不创建包装器的情况下注入您喜欢的静态方法呢?并非每种语言都支持传递函数。静态方法各有其使用情况和我使用他们当它是有道理这样做。就像经常发生的那样,这是“只是因为您不一定代表您应该这样做”。
2016年

13

您已经准确地描述了如何使用C语言进行OOP,在C语言中只有静态方法可用,“ this”是结构指针。是的,您可以通过在构造期间指定要存储为结构的一个元素的函数指针来在C中执行运行时多态。其他语言中可用的实例方法只是语法糖,实际上在后台执行了完全相同的操作。某些语言(例如python)介于两者之间,其中明确列出了“ self”参数,但是调用它的特殊语法为您处理继承和多态性。


FWIW,使用C,您可以像这样进行虚拟调度:obj->type->mthd(obj, ...);这与使用其他语言进行虚拟调度(但在幕后)基本相似。
Donal Fellows

@Karl能否请您提供某种书面参考,以开始更深入的研究?
豪尔赫·拉维恩'18

您可能希望将GObject视为一个很好的参考实现。
Karl Bielefeldt

10

当需要子类时,静态方法就会出现问题。

静态方法不能在子类中覆盖,因此新类无法提供方法的新实现,从而使它们的使用率降低。


2
不一定是问题。某些语言提供了其他机制(C ++和C#中的非虚拟方法)来有意完成同一件事-C#甚至提供了“密封”方法来进一步扩展这一思想!显然,您不会仅仅为了做到这一点而这样做,但这是一个了解的好技术……
Shog9

@Steve,这不能替代,因为您仍然可以直接调用这两种方法。

@Steve,在Java中不能重写静态方法。

@Thorbjørn-首先,该问题未标记为Java或任何其他特定的OOP语言。更重要的是,我没有说您可以覆盖静态成员函数。我试图用一个比喻来表达观点。由于我显然失败了,因此删除了评论。
Steve314,2011年

2
从某种意义上讲,它们仍然是“方法的新实现”。只是不符合标准的OOP。这感觉有点像我在说“您的措辞也不完美,nur-nur-na-nur-nur” ;-)
Steve314,2011年

7

如果声明方法static,则不必实例化类中的对象(使用new关键字)即可执行该方法。但是,除非它们也是静态的,否则您不能引用任何成员变量在这种情况下,这些成员变量属于该类,而不是该类的特定实例化对象。

换句话说,该static关键字使声明成为类定义的一部分,因此它成为该类所有实例(从该类实例化的对象)的单个参考点

随之而来的是,如果您想要类的多个运行副本,并且不希望在所有运行副本之间共享状态(成员变量)(即,每个对象都拥有自己的唯一状态),那么您无法将那些成员变量声明为静态。

通常,static只有在创建接受一个或多个参数的实用程序方法并返回某种对象而没有任何副作用(即,更改类中的状态变量)时,才应使用类和方法。


1
我认为OP理解什么static意思,他在问为什么不将所有内容都声明为静态。
Ed S.

1
传递的一个实例Subject-但作为一个明确的参数,而不是隐含this参数。
2011年

好吧,是的...但是我不明白你的意思。
Ed S.

@Ed-只有通过隐式this参数可以通过显式参数执行几乎所有操作。期望之间存在差异,但是在继承之外,您可以做的事情没有真正的差异。例如,可以通过显式参数访问那些非静态成员。
2011年

我的假设是,即使通过同一类中声明的静态方法,也无法获得C#中参数的私有成员(就像在C ++中一样)。不知道为什么我会那样想,但是我错了。
Ed S.

3

人们认为“静态方法显示不良设计”的原因不一定归因于这些方法,它实际上是隐式或显式的静态数据。隐式是指程序中不包含在返回值或参数中的任何状态。在静态函数中修改状态是对过程编程的回溯。但是,如果函数不修改状态,或不将状态修改委托给组件对象函数,则它实际上是功能范式,而不是过程范式。

如果您不更改状态,则静态方法和类可以具有很多好处。确保方法纯净变得容易得多,因此没有副作用,并且任何错误都可以完全重现。它还确保可以确保使用共享库的多个应用程序中的不同方法在相同的输入下将获得相同的结果,从而使错误跟踪和单元测试变得更加容易。

最终,在实践中,“ StateManager”等大型对象与静态或全局数据之间实际上没有什么区别。正确使用静态方法的好处是,它们可以表明作者不打算将状态修改为以后的修订者。


1
您的声明“ ...是对过程编程的回溯...”使我认为您认为它是如此糟糕,我认为它在某种意义上是OO的一部分。
NoChance 2012年

making ... unit testing much easier.不,不会。它使调用静态方法的任何代码都无法进行单元测试,因为您无法将其与静态方法隔离。调用静态方法将成为对该类的硬编码依赖项。
StuperUser 2012年

2

取决于语言和您要尝试执行的操作,答案可能是差异不大,但是static版本更加混乱。

正如您的示例语法建议使用Java或C#一样,我认为ThorbjørnRavn Andersen指出了覆盖(后期绑定)问题是正确的。对于静态方法,没有后期绑定的“特殊”参数,因此您不能进行后期绑定。

实际上,静态方法只是模块中的一个函数,该模块与类具有相同的名称-根本不是真正的OOP方法。


2

当您这样做时,您基本上是在击败对象的全部。有一个很好的理由,我们已经从程序代码转变为面向对象的代码。

永远不会声明将类类型作为参数的静态方法。这只会使代码变得更加复杂,毫无收获。


3
如果将传递objectstatic方法并返回new,则可以这样做object。函数式程序员一直在这样做。
罗伯特·哈维

问题:您是否将Math静态类视为“无收益的复杂类”,或者如果所有数字类型都具有定义绝对值,取反,加法,平方,任意根等的虚方法,则您希望使用该类吗?我不这么认为。当您需要存储隐藏的可变状态并需要强制执行一些不变量时,对象会很整洁。将对一个类型进行操作的所有可构想的方法融合到类型本身中,这会导致复杂且不可维护的代码。我同意Robert的观点,您可能想看看函数式程序员是如何工作的,它可以让您大开眼界!
萨拉

@kai正如我所说,差不多。Math类是一个合理的例外,尽管将其附加到数字类型上并不是一个坏主意。
罗伦·佩希特尔

2

这里有很多很棒的答案,而且比我想出的任何答案都具有更深刻的见解和知识,但是我觉得有些地方还没有解决:

“我们的架构师决定我们对所有API使用所有静态方法。他的理由是静态方法重量轻,并且通过降低服务器负载使Web应用程序受益。”

(强调是我的)

我在问题的这一部分的2c是这样的:

从理论上讲,这是对的:调用静态方法仅具有实际调用该方法的开销。调用非静态(实例)方法会产生额外的开销,即首先实例化对象,然后在某个时候销毁实例(手动或通过某种形式的自动垃圾收集,具体取决于所使用的平台)。

恶魔的拥护者在这方面扮演了一个小角色:我们可以走得更远,说出一些话:

  • 如果为(instance)方法的每次调用创建一个实例,这可能会变得非常糟糕(而不是仅将其保持静态并像这样调用它)

  • 根据构造函数的复杂性,类型层次结构,其他实例成员以及其他此类未知因素,非静态方法调用的额外开销可能会有所不同,并且会变得非常大

实际上,恕我直言,以上几点(以及“因为它们更快,所以请使用静态变量”的一般方法)是稻草人的论点/评估:

  • 如果代码是好的,并且更具体地针对此参数,如果仅在必要时创建实例(并以最佳方式销毁实例),则在适当的时候使用insance方法不会产生任何额外的开销(因为如果您仅创建所需的对象,即使该方法被声明为静态方法(在其他情况下=实例化开销相同),那些对象也将被创建)

  • 通过以这种方式滥用静态方法声明,就可以隐藏有关实例创建方式的代码问题(由于该方法是静态的,因此不正确的实例化代码可能直到后来才被忽略,这永远都是不好的)。


2

这是一个非常有趣的问题,根据您所询问的社区,该问题往往会有不同的答案。其他答案似乎是C#或Java偏见:(主要是)纯粹面向对象的编程语言,并且对什么是面向对象有一种惯用的看法。

在其他社区(例如C ++)中,面向对象的解释具有更大的自由度。一些众所周知的专家研究了该主题,并得出结论,自由功能可改善封装(重点是我的):

现在我们已经看到,衡量类中封装量的一种合理方法是计算如果类的实现发生变化可能破坏的函数数量。在这种情况下,很明显,具有n个成员函数的类比具有n + 1个成员函数的类封装得更多。这种观察证明了我的观点,即我倾向于使用非成员非朋友功能而不是成员功能

斯科特·迈耶斯(Scott Meyers)

对于那些不熟悉C ++术语的人:

  • 成员函数=方法
  • 非成员函数=静态方法
  • 非朋友=仅使用公共API
  • 非成员非朋友函数=仅使用公共API的静态方法

现在,回答您的问题:

我不能只使用所有静态方法吗?

不,您不应该始终使用静态方法。

但是,只要您只需要使用类的公共 API,就应该使用它们。


-3

在Java中

静态方法不会被覆盖,而是被隐藏,因此在扩展类的情况下会有很大的不同(问题?)。

另一方面,我注意到Java程序员倾向于认为所有对象都是对象,而没有真正理解原因,因此我不同意。

静态应该用于特定于该类的代码。静态不适用于继承。

使用静态方法的一些很好的例子是独立的函数,没有副作用。

还可以看到关键字sync ...


2
扩展类时,该静态方法如何隐藏?这种区别和问题将在哪里出现?同步如何与此相适应?在哪里会遇到静态和继承问题?您能否在核心Java库中提供静态方法的优缺点,并解释每种情况的原因?
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.