使用大量静态方法是一件坏事吗?
Posted
技术标签:
【中文标题】使用大量静态方法是一件坏事吗?【英文标题】:Is using a lot of static methods a bad thing? 【发布时间】:2010-10-19 15:51:14 【问题描述】:当类不需要跟踪内部状态时,我倾向于将类中的所有方法声明为静态。例如,如果我需要将 A 转换为 B 并且不依赖于可能会发生变化的某些内部状态 C,我会创建一个静态转换。如果有一个我希望能够调整的内部状态 C,那么我添加一个构造函数来设置 C 并且不使用静态转换。
我阅读了各种关于不要过度使用静态方法的建议(包括关于 ***),但我仍然无法理解上述经验法则有什么问题。
这是否合理?
【问题讨论】:
是的。看看这个:yegor256.com/2014/05/05/oop-alternative-to-utility-classes.html 【参考方案1】:常见的静态方法有两种:
“安全”静态方法将始终为相同的输入提供相同的输出。它不修改任何全局变量,也不调用任何类的任何“不安全”静态方法。本质上,您使用的是有限类型的函数式编程——不要害怕这些,它们很好。 “不安全”静态方法会改变全局状态,或代理到全局对象,或其他一些不可测试的行为。这些是过程式编程的回归,应尽可能重构。“不安全”静态有一些常见用途——例如,在 Singleton 模式中——但请注意,尽管您称呼它们的名称很漂亮,但您只是在改变全局变量。在使用不安全的静态变量之前请仔细考虑。
【讨论】:
这正是我必须解决的问题——单例对象的使用,或者更确切地说是误用。 感谢您的出色回答。我的问题是,如果将单例作为参数传递给静态方法,这会使静态方法不安全吗? 术语“纯函数”和“不纯函数”是函数式编程中给您所谓的“安全”和“不安全”静态的名称。 这是错误的。没有所谓的“不安全”静态方法。问题是改变全局状态,你可以在任何地方做。改变全局状态对于静态函数、方法、构造函数和其他任何代码可以存在的地方都是不好的。【参考方案2】:没有任何内部状态的对象是可疑的。
通常,对象封装状态和行为。只封装行为的对象是奇怪的。有时它是 Lightweight 或 Flyweight 的示例。
其他时候,它是用对象语言完成的程序设计。
【讨论】:
我听到你在说什么,但是像 Math 对象这样的东西怎么能封装除了行为之外的任何东西呢? 他只是说可疑,没有错,而且他是绝对正确的。 @JonoW:数学是一个非常特殊的情况,其中有许多无状态函数。当然,如果你在 Java 中进行函数式编程,你会有很多无状态函数。【参考方案3】:这实际上只是 John Millikin 出色回答的后续。
尽管将无状态方法(几乎是函数)设为静态是安全的,但有时会导致难以修改的耦合。假设您有这样一个静态方法:
public class StaticClassVersionOne
public static void doSomeFunkyThing(int arg);
你称之为:
StaticClassVersionOne.doSomeFunkyThing(42);
这一切都很好,也很方便,直到您遇到必须修改静态方法的行为的情况,并发现您与StaticClassVersionOne
紧密绑定。可能您可以修改代码,这会很好,但如果有其他调用者依赖于旧行为,则需要在方法体中考虑它们。在某些情况下,如果方法体试图平衡所有这些行为,它可能会变得非常丑陋或无法维护。如果您拆分方法,您可能需要在多个地方修改代码以考虑到它,或者调用新的类。
但是考虑如果你已经创建了一个接口来提供方法,并将它提供给调用者,现在当行为必须改变时,可以创建一个新的类来实现这个接口,它更干净,更容易测试,并且更易于维护,而是提供给调用者。在这种情况下,调用类不需要更改甚至重新编译,并且更改已本地化。
这可能是,也可能不是,但我认为值得考虑。
【讨论】:
我认为这不仅是一种可能的情况,这使得静力学成为最后的手段。静态也使 TDD 成为一场噩梦。无论你在哪里使用静态,你都无法模拟,你必须知道输入和输出是什么才能测试一个不相关的类。现在,如果您更改静态的行为,您对使用该静态的不相关类的测试将被破坏。此外,它成为一个隐藏的依赖项,您无法将其传递给构造函数以通知开发人员潜在的重要依赖项。【参考方案4】:另一种选择是将它们作为非静态方法添加到原始对象上:
即改变:
public class BarUtil
public static Foo transform(Bar toFoo) ...
进入
public class Bar
...
public Foo transform() ...
但是在许多情况下这是不可能的(例如,从 XSD/WSDL/等生成常规类代码),否则它会使类变得很长,并且转换方法对于复杂的对象来说往往是一个真正的痛苦,而你只希望他们在自己单独的班级中。所以是的,我在实用程序类中有静态方法。
【讨论】:
【参考方案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
查看 TextureManager,您无法通过查看构造函数来判断必须执行哪些初始化步骤。您必须深入研究该类以找到它的依赖关系并以正确的顺序初始化事物。在这种情况下,它需要在运行之前初始化 ResourceLoader。现在扩大这种依赖关系的噩梦,您可能会猜到会发生什么。想象一下,试图在没有明确的初始化顺序的情况下维护代码。将此与使用实例的依赖注入进行对比——在这种情况下,如果依赖不满足,代码甚至不会编译!
此外,如果您使用修改状态的静力学,它就像纸牌屋。你永远不知道谁可以访问什么,而且设计往往类似于意大利面条怪物。
最后,同样重要的是,使用静态将程序与特定实现联系起来。静态代码是可测试性设计的对立面。测试充满静态的代码是一场噩梦。静态调用永远不能被替换为测试替身(除非您使用专门设计用于模拟静态类型的测试框架),因此静态系统会使使用它的所有内容都成为即时集成测试。
简而言之,静态对于某些东西和小型工具或一次性代码来说是很好的,我不会阻止它们的使用。然而,除此之外,它们对于可维护性、良好的设计和易于测试来说是一场血腥的噩梦。
这里有一篇关于问题的好文章:http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/
【讨论】:
【参考方案6】:这似乎是一个合理的方法。您不想使用太多静态类/方法的原因是您最终会远离面向对象编程,而更多地进入结构化编程领域。
在您只是将 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:将函数式编程更改为结构化编程(出于某种原因我感到困惑),托斯滕指出这一点。
【讨论】:
我不认为使用静态方法有资格作为函数式编程,所以我猜你的意思是结构化编程。【参考方案7】:警告您远离静态方法的原因是使用它们会丧失对象的优势之一。对象用于数据封装。这可以防止发生意外的副作用,从而避免错误。静态方法没有封装数据*,因此无法获得此好处。
也就是说,如果您不使用内部数据,它们可以使用并且执行速度稍快一些。但请确保您没有触及其中的全局数据。
某些语言还具有允许封装数据和静态方法的类级变量。【讨论】:
【参考方案8】:我最近重构了一个应用程序以删除/修改一些最初作为静态类实现的类。随着时间的推移,这些类获得了很多,人们只是不断将新函数标记为静态,因为从来没有一个实例浮动。
所以,我的回答是静态类本身并不坏,但现在开始创建实例可能更容易,然后必须稍后重构。
【讨论】:
【参考方案9】:我认为这是一种设计气味。如果您发现自己主要使用静态方法,那么您可能没有很好的 OO 设计。这不一定很糟糕,但就像所有气味一样,它会让我停下来重新评估。它暗示您可能能够做出更好的 OO 设计,或者您应该转向另一个方向并完全避免针对此问题的 OO。
【讨论】:
【参考方案10】:嗯,当然没有灵丹妙药。静态类适用于小实用程序/帮助程序。但是使用静态方法进行业务逻辑编程肯定是邪恶的。考虑以下代码
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
隔离(大多数测试工具不模拟静态类),这使得单元测试成为不可能。没有单元测试 == 低质量
【讨论】:
【参考方案11】:我曾经在一个有一堆静态方法的类和一个单例之间来回切换。两者都解决了问题,但是可以更容易地用多个单例替换单例。 (程序员似乎总是如此确定,某事只会有 1 个,而我发现自己错了很多次,以至于完全放弃了静态方法,除非在一些非常有限的情况下)。
无论如何,单例使您能够稍后将某些内容传递到工厂以获取不同的实例,并且无需重构即可更改整个程序的行为。将静态方法的全局类更改为具有不同“支持”数据或稍微不同的行为(子类)的东西是一个很大的麻烦。
而静态方法没有类似的优势。
是的,它们很糟糕。
【讨论】:
【参考方案12】:只要不是内部状态起作用,就可以了。请注意,通常静态方法应该是线程安全的,因此如果您使用辅助数据结构,请以线程安全的方式使用它们。
【讨论】:
【参考方案13】:如果你知道你将永远需要使用 C 的内部状态,那很好。但是,如果将来发生变化,您需要使该方法成为非静态方法。如果一开始是非静态的,如果不需要,可以忽略内部状态。
【讨论】:
【参考方案14】:如果它是一个实用方法,最好将其设为静态。 Guava 和 Apache Commons 就是建立在这个原则之上的。
我对此的看法纯粹是务实的。如果是您的应用程序代码,静态方法通常不是最好的选择。静态方法有严重的单元测试限制——它们不容易被模拟:你不能将模拟的静态功能注入到其他测试中。您通常也不能将功能注入静态方法。
因此,在我的应用程序逻辑中,我通常会调用类似静态实用程序的小型方法。 IE。
static cutNotNull(String s, int length)
return s == null ? null : s.substring(0, length);
其中一个好处是我不测试这些方法:-)
【讨论】:
【参考方案15】:即使对于无状态代码,静态方法通常也是一个糟糕的选择。而是使用这些方法创建一个单例类,该类被实例化一次并注入到那些想要使用这些方法的类中。这样的类更容易模拟和测试。它们更加面向对象。您可以在需要时用代理包装它们。静态使 OO 变得更加困难,我认为几乎没有理由在所有情况下都使用它们。不是100%,但几乎全部。
【讨论】:
以上是关于使用大量静态方法是一件坏事吗?的主要内容,如果未能解决你的问题,请参考以下文章