代码之家  ›  专栏  ›  技术社区  ›  Lolo

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

  •  83
  • Lolo  · 技术社区  · 16 年前

    当类不需要跟踪内部状态时,我倾向于将类中的所有方法声明为静态的。例如,如果我需要将a转换为b,并且不依赖于可能会变化的一些内部状态c,那么我创建一个静态转换。如果有一个内部状态C我想能够调整,那么我添加了一个构造函数来设置C,并且不使用静态转换。

    我阅读了各种建议(包括StackOverflow),不要过度使用静态方法,但我仍然无法理解上面的经验法则有什么问题。

    这是不是一个合理的方法?

    15 回复  |  直到 7 年前
        1
  •  135
  •   Kim Desrosiers Pardeep Jain    7 年前

    有两种常见的静态方法:

    • “安全”静态方法总是为相同的输入提供相同的输出。它不修改全局,也不调用任何类的任何“不安全”静态方法。本质上,您使用的是一种有限的函数式编程——不要害怕这些,它们很好。
    • 一个“不安全”的静态方法会改变全局状态、全局对象的代理或其他一些不可测试的行为。这些都是过程编程的基础,如果可能的话,应该重构。

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

        2
  •  14
  •   S.Lott    16 年前

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

    通常,对象封装状态和行为。只封装行为的对象是奇数的。有时它是 轻量化 飞锤 .

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

        3
  •  11
  •   Grundlefleck anujkk    16 年前

    这真的只是约翰米利金伟大答案的后续行动。


    虽然可以安全地将无状态方法(相当多的函数)设置为静态,但有时会导致难以修改的耦合。假设您有一个静态方法,例如:

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

    你称之为:

    StaticClassVersionOne.doSomeFunkyThing(42);
    

    这一切都很好,而且非常方便,直到您遇到一个必须修改静态方法行为的情况,并且发现您与 StaticClassVersionOne . 可能您可以修改代码,这样就可以了,但是如果有其他调用程序依赖于旧的行为,则需要在方法体中考虑它们。在某些情况下,如果方法体试图平衡所有这些行为,它可能会变得非常丑陋或无法维护。如果拆分方法,可能需要在多个地方修改代码才能考虑到它,或者调用新的类。

    但是考虑一下,如果您已经创建了一个接口来提供该方法,并将其提供给调用方,那么现在当行为必须更改时,可以创建一个新的类来实现该接口,该接口更干净、更容易测试和更可维护,而该类将提供给调用方。在这个场景中,调用类不需要修改甚至重新编译,并且更改是本地化的。

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

        4
  •  5
  •   JeeBee    16 年前

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

    即:改变:

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

    进入之内

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

    然而,在许多情况下,这是不可能的(例如,从xsd/wsdl/etc生成常规类代码),否则它将使类非常长,而转换方法对于复杂的对象来说通常是一种真正的痛苦,您只需要将它们放在自己的类中。所以是的,我在实用程序类中有静态方法。

        5
  •  4
  •   Mark Simpson    16 年前

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

    即:“叶”方法(它们不修改状态,只是以某种方式转换输入)。例如path.combine。这些类型的东西对terser语法很有用。

    这个 问题 我有很多静态的:

    首先,如果您有静态类,依赖项是隐藏的。考虑以下事项:

    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。现在放大这个依赖性噩梦,你可能会猜到会发生什么。想象一下,在没有明确初始化顺序的情况下,试图维护代码。与依赖注入和实例相比——在这种情况下,代码甚至不会 编译 如果不满足依赖关系!

    此外,如果您使用修改状态的静态数据,它就像是一个卡片库。你永远不知道谁有权得到什么,而且设计往往像一个意大利面怪物。

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

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

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

        6
  •  3
  •   Steve Rowe    16 年前

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

    也就是说,如果您不使用内部数据,那么可以使用它们,执行速度稍微快一点。不过,请确保不要接触其中的全局数据。

    • 有些语言还具有类级变量,允许封装数据和静态方法。
        7
  •  3
  •   MunkiPhD    16 年前

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

    在您的情况下,如果您只是将a转换为b,那么我们所要做的就是将文本转换为

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

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

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

    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.NET MVC或Ruby中的HTML助手方法。它们创建的HTML元素与对象的行为无关,因此是静态的。

    编辑2:将函数式编程改为结构化编程(出于某种原因,我感到困惑),并向Torsten提供指出这一点的道具。

        8
  •  2
  •   overslacked    16 年前

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

    所以,我的答案是静态类本身并不坏,但是现在开始创建实例,然后稍后重构可能会更容易。

        9
  •  2
  •   patros    11 年前

    我认为这是设计的味道。如果您发现自己主要使用静态方法,那么您可能没有非常好的OO设计。这不一定很糟糕,但和所有气味一样,它会让我停下来重新评估。它暗示您可能能够做出一个更好的OO设计,或者您应该朝另一个方向前进,并完全避免OO出现这个问题。

        10
  •  1
  •   Lucero    16 年前

    只要不是内部状态起作用,这就可以了。请注意,通常静态方法是线程安全的,因此如果使用助手数据结构,请以线程安全的方式使用它们。

        11
  •  1
  •   Bill K    16 年前

    我曾经在一个有很多静态方法的类和一个单例类之间来回走动。两者都解决了这个问题,但单例可以更容易地替换为多个。(程序员似乎总是很确定只有一种东西,我发现自己错误的次数足够多,除了在一些非常有限的情况下,完全放弃静态方法)。

    无论如何,singleton使您能够稍后将某个东西传递到工厂以获得不同的实例,从而在不重构的情况下改变整个程序的行为。将静态方法的全局类变成不同的“支持”数据或稍微不同的行为(子类)是一个主要的麻烦。

    静态方法也没有类似的优势。

    所以是的,他们很坏。

        12
  •  1
  •   Andrey Chaschev    11 年前

    如果它是一个实用方法,最好使其静态化。瓜娃和阿帕奇公地是建立在这一原则之上的。

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

    所以在我的应用程序逻辑中,我通常有一些小的静态实用程序,比如方法调用。即。

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

    其中一个好处是我没有测试这种方法:—)

        13
  •  1
  •   Konstantin Chernov    9 年前

    当然,没有银弹。静态类对于小实用程序/帮助程序来说是可以的。但是使用静态方法进行业务逻辑编程当然是邪恶的。考虑以下代码

       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;
            }
        }
    

    您会看到一个静态方法调用 ItemsProcessor.SplitItem(newItem); 闻起来有味道

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

    如果你知道你会的话 从未 需要使用C的内部状态,没关系。不过,如果将来这种情况发生变化,您需要使该方法非静态的。如果开始时它是非静态的,那么如果不需要它,您可以忽略内部状态。

        15
  •  0
  •   John    8 年前

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