Is using a lot of static methods a bad thing?

I tend to declare as static all the methods in a class when that class doesn't require to keep track of internal states. For example, if I need to transform A into B and don't rely on some internal state C that may vary, I create a static transform. If there is an internal state C that I want to be able to adjust, then I add a constructor to set C and don't use a static transform.

I read various recommendations (including on StackOverflow) NOT to overuse static methods but I still fail to understand what it wrong with the rule of thumb above.

Is that a reasonable approach or not?

43503 次浏览

这似乎是一个合理的方法。你之所以不想使用太多的静态类/方法,是因为你最终会从面向对象的编程转移到更多的结构化编程领域。

在你只是简单地将 A 转换成 B 的情况下,假设我们所做的就是将文本从 A 转换成 B

"hello" =>(transform)=> "<b>Hello!</b>"

Then a static method would make sense.

然而,如果你经常在一个对象上调用这些静态方法,并且它对于很多调用来说是唯一的(例如,你使用它的方式取决于输入) ,或者它是对象固有行为的一部分,那么将它作为对象的一部分并维护它的状态是明智的。做到这一点的一种方法是将它实现为一个接口。

class Interface{
method toHtml(){
return transformed string (e.g. "<b>Hello!</b>")
}


method toConsole(){
return transformed string (e.g. "printf Hello!")
}
}




class Object implements Interface {
mystring = "hello"


//the implementations of the interface would yield the necessary
//functionality, and it is reusable across the board since it
//is an interface so... you can make it specific to the object


method toHtml()
method toConsole()
}

编辑: Asp 中的 html 助手方法是静态方法大量使用的一个很好的例子。网络 MVC 或 Ruby。它们创建的 html 元素与对象的行为无关,因此是静态的。

Edit 2: Changed functional programming to structured programming (for some reason I got confused), props to Torsten for pointing that out.

没有任何内部状态的对象是可疑的。

通常,对象封装状态和行为。只封装行为的对象很奇怪。有时是 轻量级的轻量级的例子。

Other times, it's procedural design done in an object language.

只要不是内部状态发挥作用,这是罚款。请注意,静态方法通常是线程安全的,因此如果使用 helper 数据结构,请以线程安全的方式使用它们。

另一种选择是将它们作为非静态方法添加到原始对象上:

i.e., changing:

public class BarUtil {
public static Foo transform(Bar toFoo) { ... }
}

进入

public class Bar {
...
public Foo transform() { ...}
}

然而在许多情况下这是不可能的(例如,从 XSD/WSDL/etc 生成常规的类代码) ,或者它会使类变得非常长,转换方法对于复杂对象来说通常是一个真正的痛苦,你只需要把它们放在它们自己独立的类中。所以,我在实用程序类中有静态方法。

不要使用静态方法的原因是,使用静态方法会丧失对象的优势之一。对象用于数据封装。这可以防止意想不到的副作用的发生,避免错误。静态方法没有封装的数据 * ,因此不能获得这种好处。

也就是说,如果您不使用内部数据,那么使用它们很方便,执行速度也会稍微快一些。但是,请确保不要触及其中的全局数据。

  • 有些语言还有类级变量,允许封装数据和静态方法。

我最近重构了一个应用程序,以删除/修改一些最初实现为静态类的类。随着时间的推移,这些类获得了如此多的信息,人们只是不断地将新函数标记为静态函数,因为从来没有一个实例漂浮在周围。

因此,我的答案是静态类本质上并不坏,但是现在开始创建实例,然后以后再重构可能更容易。

There are two kinds of common static methods:

  • 一个“安全”的静态方法总是为相同的输入提供相同的输出。它不修改任何全局变量,也不调用任何类的任何“不安全”静态方法。本质上,您正在使用一种有限的函数式编程——不要害怕这些,它们很好。
  • An "unsafe" static method mutates global state, or proxies to a global object, or some other non-testable behavior. These are throwbacks to procedural programming and should be refactored if at all possible.

“不安全”静态学有一些常见的用法——例如,在单例模式中——但是要注意,尽管你称它们为漂亮的名字,但是你只是在变化全局变量。在使用不安全静电之前要仔细考虑。

如果你知道你将 永远不会需要使用 C 的内部状态,它的罚款。但是,如果将来这种情况发生变化,您需要使该方法非静态。如果它一开始就是非静态的,那么如果不需要的话,可以忽略内部状态。

我过去常常在一个有很多静态方法的类和一个单例类之间来回切换。两者都可以解决这个问题,但是单例模式更容易被多个模式替代。(程序员似乎总是非常肯定某些东西只有一个,而且我发现自己错的次数已经够多了,所以完全放弃了静态方法,除了在一些非常有限的情况下)。

Anyway, the singleton gives you the ability to later pass something into the factory to get a different instance and that changes the behavior of your entire program without refactoring. Changing a global class of static methods into something with different "backing" data or a slightly different behavior (child class) is a major pain in the butt.

而静态方法没有类似的优点。

所以,是的,他们很坏。

这实际上只是约翰 · 米利金伟大答案的后续。


尽管使无状态方法(基本上是函数)静态化是安全的,但有时会导致难以修改的耦合。假设您有这样一个静态方法:

public class StaticClassVersionOne {
public static void doSomeFunkyThing(int arg);
}

你们称之为:

StaticClassVersionOne.doSomeFunkyThing(42);

这一切都很好,而且非常方便,直到您遇到必须修改静态方法的行为的情况,并发现您与 StaticClassVersionOne紧密绑定。也许您可以修改代码,但是如果有其他依赖于旧行为的调用方,那么它们将需要在方法体中加以说明。在某些情况下,如果方法体试图平衡所有这些行为,那么它可能会变得非常丑陋或无法维护。如果将这些方法分离出来,可能需要在几个地方修改代码以考虑它,或者调用新的类。

但是考虑一下,如果你创建了一个接口来提供这个方法,然后把它给调用者,现在当行为需要改变时,可以创建一个新的类来实现这个接口,这个接口更简洁,更容易测试,更易维护,而这个接口被给调用者。在这种情况下,调用类不需要更改,甚至不需要重新编译,而且更改是本地化的。

这可能也可能不是一个可能的情况,但我认为这是值得考虑的。

我觉得这是设计的味道。如果您发现自己使用的大多是静态方法,那么您可能没有一个非常好的 OO 设计。这并不一定是坏事,但与所有的气味一样,它会让我停下来重新评估。它暗示您可能能够做出更好的面向对象设计,或者您可能应该朝着另一个方向,完全避免这个问题的面向对象设计。

只要在正确的地方使用静态类,它们就没问题。

即: 属于“叶子”方法的方法(它们不修改状态,只是以某种方式转换输入)。这方面的一个很好的例子就是 Path。合并。这些东西非常有用,有助于更简洁的语法。

我的 问题与静电有很多:

首先,如果你有静态类,依赖关系是隐藏的:

public static class ResourceLoader
{
public static void Init(string _rootPath) { ... etc. }
public static void GetResource(string _resourceName)  { ... etc. }
public static void Quit() { ... etc. }
}


public static class TextureManager
{
private static Dictionary<string, Texture> m_textures;


public static Init(IEnumerable<GraphicsFormat> _formats)
{
m_textures = new Dictionary<string, Texture>();


foreach(var graphicsFormat in _formats)
{
// do something to create loading classes for all
// supported formats or some other contrived example!
}
}


public static Texture GetTexture(string _path)
{
if(m_textures.ContainsKey(_path))
return m_textures[_path];


// How do we know that ResourceLoader is valid at this point?
var texture = ResourceLoader.LoadResource(_path);
m_textures.Add(_path, texture);
return texture;
}


public static Quit() { ... cleanup code }
}

查看 TextureManager,您不能通过查看构造函数来判断必须执行哪些初始化步骤。你必须深入研究这个类,找到它的依赖关系,并按照正确的顺序初始化事物。在这种情况下,它需要在运行之前初始化 ResourceLoader。现在扩展这个依赖性噩梦,您可能会猜到将会发生什么。想象一下,在没有明确的初始化顺序的情况下,试图维护代码。对比一下实例依赖注入——在这种情况下,如果依赖关系没有得到满足,代码甚至不会是 编译

此外,如果你使用静力学来修改状态,它就像一个纸牌屋。你永远不知道谁能接触到什么,而且这个设计看起来就像一个意大利面怪物。

最后,同样重要的是,使用静态将程序与特定的实现联系起来。静态代码是可测试性设计的对立面。测试充斥着静态信息的代码是一场噩梦。静态调用永远不能与 test double 交换(除非您使用专门为模拟静态类型而设计的测试框架) ,因此静态系统将使用它的所有东西都变成即时集成测试。

In short, statics are fine for some things and for small tools or throwaway code I wouldn't discourage their use. However, beyond that, they are a bloody nightmare for maintainability, good design and ease of testing.

这里有一篇关于问题的好文章: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/

如果它是一个实用程序方法,那么最好使它成为静态的。

我对此的看法纯粹是务实的。如果这是你的应用程序代码,静态方法通常不是最好的选择。静态方法有严重的单元测试限制——它们不容易被模仿: 您不能将模仿的静态功能注入到其他测试中。您通常也不能将功能注入到静态方法中。

因此,在我的应用程序逻辑中,我通常有小的静态实用程序类方法调用。

static cutNotNull(String s, int length){
return s == null ? null : s.substring(0, length);
}

其中一个好处是,我不测试这样的方法: -)

当然,没有什么灵丹妙药。静态类对于小型实用程序/助手是可以的。但是使用静态方法进行业务逻辑编程肯定是有害的。考虑以下代码

   public class BusinessService
{


public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
{
var newItemId = itemsRepository.Create(createItem, userID, ownerID);
**var searchItem = ItemsProcessor.SplitItem(newItem);**
searchRepository.Add(searchItem);
return newItemId;
}
}

You see a static method call to ItemsProcessor.SplitItem(newItem); It smells cause

  • 没有声明明确的依赖项,如果不深入研究代码,可能会忽略类和静态方法容器之间的耦合
  • 您不能测试 BusinessService,将它与 ItemsProcessor隔离开来(大多数测试工具不模拟静态类) ,这使得单元测试成为不可能。没有单元测试 = = 低质量

静态方法通常是一个糟糕的选择,即使对于无状态代码也是如此。相反,使用这些方法创建一个单例类,这些方法被实例化一次并注入到那些希望使用这些方法的类中。这样的类更容易模拟和测试。它们更加面向对象。需要时,可以用代理包装它们。静态使 OO 更加困难,我认为几乎没有理由在所有情况下使用它们。不是100% ,但几乎全部。