使用大量静态方法是一件坏事吗?
-
09-09-2019 - |
题
当类不需要跟踪内部状态时,我倾向于将类中的所有方法声明为静态。例如,如果我需要将 A 转换为 B 并且不依赖于某些可能变化的内部状态 C,我会创建一个静态转换。如果我希望能够调整内部状态 C,那么我添加一个构造函数来设置 C,并且不使用静态转换。
我阅读了各种建议(包括在 StackOverflow 上)不要过度使用静态方法,但我仍然无法理解上面的经验法则有什么问题。
这是一个合理的做法吗?
解决方案
常见的静态方法有两种:
- “安全”静态方法将始终为相同的输入提供相同的输出。它不修改全局变量,也不调用任何类的任何“不安全”静态方法。本质上,您正在使用一种有限的函数式编程——不要害怕这些,它们很好。
- “不安全”的静态方法会改变全局状态、全局对象的代理或其他一些不可测试的行为。这些都是过程式编程的倒退,如果可能的话应该进行重构。
“不安全”静态有一些常见的用途——例如,在单例模式中——但要注意,尽管你给它们起了任何漂亮的名字,但你只是在改变全局变量。在使用不安全的静力学之前请仔细考虑。
其他提示
没有任何内部状态的对象是一个可疑的事情。
通常,对象封装状态和行为。只有封装行为的对象是奇数。有时它的轻型强>或飞锤强>的一个例子。
其他时候,它的程序设计中的对象的语言实现。
这是真的只有跟进约翰·米利金的伟大的答案。
虽然它可以是安全的,使无状态的方法(这是相当多的功能)静态的,它有时会导致耦合是很难修改。考虑你有一个静态方法,例如:
public class StaticClassVersionOne {
public static void doSomeFunkyThing(int arg);
}
您拨打如下:
StaticClassVersionOne.doSomeFunkyThing(42);
这是一切都很好,很方便,直到你遇到,你必须修改静态方法的行为,并发现你紧紧结合StaticClassVersionOne
的情况下。也许你可以修改代码,这将是很好,但如果有依赖于旧行为的其他来电,他们将需要在方法体进行会计处理。在某些情况下,如果它试图平衡所有这些行为,方法体可以得到相当丑陋或难以维护。如果拆分出来的方法,你可能需要修改的代码在几个地方利用它的帐户,或者做出新的类调用。
但是,如果你创造了一个接口,提供方法考虑,它给调用者,现在当行为必须改变,一类新的可以创建实现接口,这是更清洁,更容易测试,更易于维护,而且反而是给调用者。在这种情况下,调用类不需要改变或重新编译,甚至和变化是局部的。
它可能会或可能不会是一个可能出现的情况,但我认为这是值得考虑的。
另一种选择是将其添加为始发物上的非静态方法:
即,改变:
public class BarUtil {
public static Foo transform(Bar toFoo) { ... }
}
到
public class Bar {
...
public Foo transform() { ...}
}
然而,在许多情况下,这是不可能的(例如,从XSD / WSDL /等常规类别代码生成),否则就会使课堂很长,转化方法往往是复杂的对象和你一个真正的痛苦只是希望他们在自己单独的类。所以是的,我在工具类的静态方法。
警告您远离静态方法的原因是,使用它们会丧失对象的优点之一。对象用于数据封装。这可以防止发生意外的副作用,从而避免错误。静态方法没有封装数据*,因此无法获得此好处。
也就是说,如果您不使用内部数据,它们就可以很好地使用并且执行速度稍快一些。但请确保您没有触及其中的全局数据。
- 某些语言还具有类级变量,可以封装数据和静态方法。
,因为它们可以在适当的地方使用静态类是细如长。
即:中的方法是“叶”的方法(它们不修改状态时,它们仅仅以某种方式变换输入)。就是很好的例子是一样的东西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,你不能告诉初始化步骤必须寻找一个构造函数来进行。你必须深入到类找到它的依赖,并以正确的顺序初始化的事情。在这种情况下,需要在运行前被初始化的资源加载。现在扩大这种依赖恶梦,你可能已经猜到会发生什么。试想一下那里是初始化没有明确以保持代码。与依赖注入实例对比这 - !在这种情况下的代码甚至不会编译如果依赖不履行
此外,如果您使用修改状态静态,它像纸牌做的房子。你永远不知道谁访问了什么,以及设计趋向于像意大利面条怪物。
最后,同样重要的是,使用静态捆绑的程序以一个特定的实施方式。静态代码是可测试性设计的对立面。测试代码是充斥着静是一场噩梦。静态调用不能被交换为测试双(除非你使用的测试框架专门设计用来模拟出静态类型),所以静态系统使使用它是一个即时集成测试一切。
在短期,静态的罚款对一些事情和小工具或一次性的代码,我不会阻止他们使用。然而,除此之外,他们是为维护,良好的设计和便于测试血腥的恶梦。
下面是对问题的好文章:的 http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/
这似乎是一种合理的方法。你不想用过多的静态类/方法的原因是,你最终移动从面向对象的编程和更掳到结构化编程的境界。
在你的情况,你只是变换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指出了这一点
我最近重构应用程序删除/修改已被最初实现为静态类一些类。随着时间的推移,这些类获得了这么多,人们只是不停地标记的新功能为静态的,因为从来就没有一个实例左右浮动。
所以,我的回答是静态类本质上并不坏,但它可能是更容易现在开始创建实例,然后让后来重构。
我去来回类之间与一群的静态方法和一个单。这两种解决问题,但单可以与不止一个更容易被取代。 (程序员总是显得那么肯定只会有东西1,我发现自己错了足够的时间来完全放弃除了在某些非常有限的情况下,静态方法)。
总之,单给你以后的东西传递到工厂来获得不同的实例的能力,改变你的整个程序的行为,而重构。更改全局类的静态方法与不同“撑腰”的数据或略微不同的行为(子类)的东西是在对接的一大痛苦。
和静态方法没有类似的优势。
所以,是的,他们是坏的。
我认为这是一个设计的气味。如果你发现自己大多采用静态方法,你可能不会有很好的面向对象的设计。这不一定是坏事,但与所有的气味它会使我停止和重新评估。这暗示你也许可以做出更好的面向对象设计,或者说也许你应该去另一个方向,避免OO完全是为了这个问题。
只要不是内部状态进场时,这是好的。需要注意的是,通常静态方法,预计是线程安全的,所以如果你使用的辅助数据结构,在一个线程安全的方式使用它们。
如果你知道你会的从不的需要使用C的内部状态,它的罚款。应该是有史以来在将来改变,不过,你需要做的方法非静态。如果它的非静态的,首先,你可以忽略的内部状态,如果你不需要它。
如果这是一个实用的方法,这是不错的,使其静态的。番石榴和Apache共享是根据这一原理构建的。
我对这个问题的看法是纯粹务实。如果这是你的应用程序代码,静态方法一般不会有最好的东西。静态方法有严重的单元测试的局限性 - 他们不容易被嘲笑:你不能注入一个嘲笑静态功能集成到其他一些测试。还通常不能注入功能集成到一个静态方法。
因此,在我的应用程序逻辑我通常有小的静态实用样的方法调用。即
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;
}
}
您会看到一个静态方法调用 ItemsProcessor.SplitItem(newItem);
闻起来有原因
- 您没有声明显式依赖项,如果您不深入研究代码,您可能会忽略类和静态方法容器之间的耦合
- 你无法测试
BusinessService
将其隔离于ItemsProcessor
(大多数测试工具不会模拟静态类),这使得单元测试变得不可能。没有单元测试==低质量
静态方法通常即使对无状态代码不好的选择。相反,做一个单身类被实例化一次,并注入到那些想使用的方法类这些方法。这样的类更容易嘲笑和测试。他们更面向对象。需要时您可以用代理包装起来。静使OO更难,我看不出有任何理由在几乎所有情况下使用它们。不是100%,但几乎所有的。