ChatGPT解决这个技术问题 Extra ChatGPT

使用大量静态方法是一件坏事吗?

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

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

这是一个合理的方法吗?


K
Kim Desrosiers

常见的静态方法有两种:

“安全”的静态方法将始终为相同的输入提供相同的输出。它不修改任何全局变量,也不调用任何类的任何“不安全”静态方法。从本质上讲,您正在使用一种有限的函数式编程——不要害怕这些,它们很好。

“不安全”的静态方法会改变全局状态,或代理到全局对象,或其他一些不可测试的行为。这些是对过程编程的回归,如果可能的话,应该重构。

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


这正是我必须解决的问题——Singleton 对象的使用,或者更确切地说是误用。
感谢您提供最出色的答案。我的问题是,如果单例作为参数传递给静态方法,这会使静态方法不安全吗?
术语“纯函数”和“不纯函数”是函数式编程中给您所谓的“安全”和“不安全”静态变量的名称。
这是错误的。没有所谓的“不安全”静态方法。问题是改变全局状态,你可以在任何地方做。突变全局状态对于静态函数是不好的,对于方法是不好的,对于构造函数是不好的,在任何其他代码可以存在的地方都是不好的。
S
S.Lott

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

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

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


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

这实际上只是约翰米利金出色答案的后续行动。

尽管将无状态方法(几乎是函数)设为静态是安全的,但有时会导致难以修改的耦合。考虑你有一个这样的静态方法:

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

你称之为:

StaticClassVersionOne.doSomeFunkyThing(42);

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

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

这可能是,也可能不是,但我认为值得考虑。


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

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

即,改变:

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

进入

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

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


M
Mark Simpson

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

即:“叶”方法的方法(它们不修改状态,它们只是以某种方式转换输入)。 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 }       
}

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

此外,如果你使用修改状态的静态,它就像一个纸牌屋。您永远不知道谁可以访问什么,并且设计往往类似于意大利面条怪物。

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

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

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


M
MunkiPhD

这似乎是一种合理的做法。您不想使用太多静态类/方法的原因是您最终会远离面向对象的编程,而更多地进入结构化编程领域。

在您只是将 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:将函数式编程更改为结构化编程(出于某种原因,我感到困惑),托斯滕指出这一点。


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

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

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

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


o
overslacked

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

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


p
patros

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


K
Konstantin Chernov

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

   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 隔离开来进行测试(大多数测试工具不会模拟静态类),这使得单元测试变得不可能。没有单元测试 == 低质量


B
Bill K

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

无论如何,单例使您能够稍后将某些内容传递给工厂以获取不同的实例,并且无需重构即可更改整个程序的行为。将全局静态方法类更改为具有不同“支持”数据或稍微不同的行为(子类)的东西是一个主要的痛苦。

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

所以,是的,他们很糟糕。


L
Lucero

只要没有内部状态发挥作用,就可以了。请注意,通常静态方法应该是线程安全的,因此如果您使用辅助数据结构,请以线程安全的方式使用它们。


A
Adam Crume

如果你知道你永远不需要使用 C 的内部状态,那很好。但是,如果将来发生变化,您需要使该方法成为非静态方法。如果一开始是非静态的,那么如果你不需要它,你可以忽略内部状态。


A
Andrey Chaschev

如果它是一种实用方法,最好将其设为静态。 Guava 和 Apache Commons 就是建立在这个原则之上的。

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

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

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

好处之一是我不测试这些方法:-)


J
John

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