使用很多静态方法是件坏事吗?

Lol*_*olo 90 language-agnostic static-methods

当该类不需要跟踪内部状态时,我倾向于将类中的所有方法声明为静态.例如,如果我需要将A转换为B并且不依赖于可能变化的某个内部状态C,则创建静态转换.如果有一个我希望能够调整的内部状态C,那么我添加一个构造函数来设置C并且不使用静态转换.

我阅读了各种建议(包括在StackOverflow上),不要过度使用静态方法,但我仍然无法理解上面的经验法则错误.

这是一种合理的方法吗?

Joh*_*kin 145

有两种常见的静态方法:

  • "安全"静态方法总是为相同的输入提供相同的输出.它不修改全局变量,也不会调用任何类的任何"不安全"静态方法.从本质上讲,你使用的是有限类型的函数式编程 - 不要害怕这些,它们很好.
  • "不安全"的静态方法会将全局状态或代理转换为全局对象或其他一些不可测试的行为.这些是程序编程的回归,如果可能的话应该重构.

"不安全"静态有一些常见的用法 - 例如,在Singleton模式中 - 但要注意尽管你称之为任何漂亮的名字,但你只是在改变全局变量.在使用不安全的静态之前要仔细考虑.

  • 术语“纯函数”和“不纯函数”是函数式编程中对“安全”和“不安全”静态的命名。 (2认同)
  • 这是错误的。不存在“不安全”的静态方法。问题是改变全局状态,你可以在任何地方做到这一点。改变全局状态对于静态函数来说是不好的,对于方法来说是不好的,对于构造函数来说是不好的,对于代码可以存在的任何其他地方来说都是不好的。 (2认同)

S.L*_*ott 18

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

通常,对象封装状态和行为.仅封装行为的对象是奇怪的.有时它是轻量级轻量级的一个例子.

其他时候,它是用对象语言完成的程序设计.

  • 他只是说可疑,没有错,他是绝对正确的. (9认同)
  • 我听到你在说什么,但是像Math对象这样的东西怎么能封装除行为之外的东西呢? (6认同)
  • @JonoW:数学是一个非常特殊的情况,其中有许多无状态函数.当然,如果您在Java中进行函数式编程,那么您将拥有许多无状态函数. (2认同)

Gru*_*eck 13

这实际上只是John Millikin的一个很好的答案.


虽然将无状态方法(几乎是函数)静态化是安全的,但有时会导致很难修改的耦合.考虑一下你有一个静态方法:

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}
Run Code Online (Sandbox Code Playgroud)

您称之为:

StaticClassVersionOne.doSomeFunkyThing(42);
Run Code Online (Sandbox Code Playgroud)

这一切都很好,非常方便,直到你遇到一个必须修改静态方法行为的情况,并发现你是紧紧绑定的StaticClassVersionOne.可能你可以修改代码并且没问题,但如果有其他调用者依赖于旧行为,则需要在方法体中考虑它们.在某些情况下,如果方法体试图平衡所有这些行为,那么它可能变得非常难看或不可维护.如果您拆分方法,则可能需要在多个位置修改代码以考虑它,或者调用新类.

但是考虑一下你是否创建了一个接口来提供方法,并将其提供给调用者,现在当行为必须改变时,可以创建一个新类来实现接口,这个接口更干净,更容易测试,更易于维护,而是给予呼叫者.在这种情况下,调用类不需要更改甚至重新编译,并且更改已本地化.

它可能或可能不是可能的情况,但我认为值得考虑.

  • 我认为这不仅是一种可能的情况,这使得静力学成为最后的手段.静态技术使TDD成为一场噩梦.无论你在哪里使用静态,你都无法模拟,你必须知道输入和输出是什么来测试一个不相关的类.现在,如果更改静态的行为,则对使用该静态的不相关类的测试会被破坏.此外,它成为隐藏的依赖项,您无法传递构造函数以通知开发人员可能重要的依赖项. (5认同)

Jee*_*Bee 6

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

即改变:

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}
Run Code Online (Sandbox Code Playgroud)

public class Bar {
    ...
    public Foo transform() { ...}
}
Run Code Online (Sandbox Code Playgroud)

但是在许多情况下这是不可能的(例如,从XSD/WSDL /等生成常规类代码),或者它会使类很长,并且转换方法通常对复杂对象来说是一个真正的痛苦,你只需要它们在他们自己的单独课堂上.所以是的,我在实用程序类中有静态方法.


Mun*_*PhD 5

这似乎是一个合理的方法。您不想使用太多静态类/方法的原因是您最终会从面向对象的编程转向结构化编程领域。

在您只是将 A 转换为 B 的情况下,请说我们所做的只是将文本转换为

"hello" =>(transform)=> "<b>Hello!</b>"
Run Code Online (Sandbox Code Playgroud)

那么静态方法就有意义了。

但是,如果您经常在一个对象上调用这些静态方法并且它对于许多调用来说往往是唯一的(例如,您使用它的方式取决于输入),或者它是对象固有行为的一部分,它会明智的做法是让它成为对象的一部分并保持它的状态。一种方法是将其实现为接口。

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()
}
Run Code Online (Sandbox Code Playgroud)

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

编辑 2:将函数式编程更改为结构化编程(出于某种原因,我感到困惑),感谢 Torsten 指出这一点。

  • 我不认为使用静态方法有资格作为函数式编程,所以我猜你的意思是结构化编程。 (2认同)

Ste*_*owe 5

警告您远离静态方法的原因是使用它们会失去对象的优势之一。对象用于数据封装。这可以防止发生意外的副作用,从而避免错误。静态方法没有封装数据*,因此不会获得这种好处。

也就是说,如果您不使用内部数据,它们可以很好地使用并且执行速度稍快一些。确保您没有接触其中的全局数据。

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


Mar*_*son 5

只要在正确的地方使用静态类,就可以了。

即:作为“叶”方法的方法(它们不修改状态,它们只是以某种方式转换输入)。诸如Path.Combine之类的例子就是很好的例子。这些事情很有用,有助于简化语法。

问题我有静有很多:

首先,如果您有静态类,则依赖项将被隐藏。考虑以下:

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 }       
}
Run Code Online (Sandbox Code Playgroud)

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

此外,如果您使用修改状态的静态变量,则就像纸牌屋。您永远都不知道谁可以访问什么,而且设计趋向于像意大利面条怪物。

最后,同样重要的是,使用静态方法会将程序与特定的实现联系在一起。静态代码是可测试性设计的对立面。测试充满静电的代码是一场噩梦。静态调用永远不能交换为测试双重测试(除非您使用专门设计用于模拟静态类型的测试框架),因此静态系统会使使用它的所有内容都成为即时集成测试。

简而言之,对于某些事物,小型工具或一次性代码,静电都很好,我不会阻止它们的使用。但是,除此之外,它们对于可维护性,良好的设计和易于测试来说是一场血腥的噩梦。

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


Kon*_*nov 5

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

   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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

你看到一个静态方法调用ItemsProcessor.SplitItem(newItem);它闻到了原因

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