使用了很多静态方法是一件坏事?
当这个类不需要跟踪内部状态时,我倾向于将类中的所有方法声明为静态。 例如,如果我需要将A转换为B,并且不依赖于某些可能不同的内部状态C,则创build一个静态转换。 如果有一个内部状态C,我希望能够调整,那么我添加一个构造函数来设置C,不要使用静态转换。
我读了各种build议(包括在StackOverflow)不要过度使用静态方法,但我仍然不明白上面的经验法则是什么问题。
这是一个合理的方法吗?
有两种常见的静态方法:
- 一个“安全的”静态方法将总是给相同的input相同的输出。 它不修改全局variables,也不调用任何类的任何“不安全”的静态方法。 从本质上讲,你正在使用有限的函数式编程 – 不要害怕这些,他们没事。
- 一个“不安全的”静态方法会改变全局状态,或代理全局对象,或者其他一些不可testing的行为。 这些是程序编程的倒退,如果可能的话应该重构。
“不安全”静态数据的一些常见用法(例如,在单例模式中),但请注意,尽pipe有任何漂亮的名称可以称为它们,但您只是在变异全局variables。 在使用不安全的静力学之前要仔细考虑。
没有任何内部状态的对象是一个可疑的事情。
通常,对象封装了状态和行为。 一个只封装行为的对象很奇怪。 有时候,这是一个轻量级或享元的例子。
其他时候,这是用对象语言完成的程序devise。
这只是John Millikin伟大的答案的后续。
尽pipe无状态方法(function非常多)可以是安全的,但它有时会导致很难修改的耦合。 考虑你有一个静态的方法:
public class StaticClassVersionOne { public static void doSomeFunkyThing(int arg); }
你称之为:
StaticClassVersionOne.doSomeFunkyThing(42);
这是一切都很好,很方便,直到遇到一个情况,你必须修改静态方法的行为,并发现你是紧紧地绑定到StaticClassVersionOne
。 可能你可以修改代码,这样可以,但是如果还有其他的调用者依赖于旧的行为,那么他们需要在方法体内进行考虑。 在某些情况下,如果方法体试图平衡所有这些行为,就会变得相当丑陋或不可维护。 如果你拆分了方法,你可能需要在几个地方修改代码来考虑它,或者调用新的类。
但是考虑一下,如果你已经创build了一个接口来提供方法,并将其提供给调用者,那么现在当行为必须改变时,可以创build一个新类来实现接口,这个接口更简洁,更容易testing,更易于维护,而是给予呼叫者。 在这种情况下,调用类不需要改变,甚至不需要重新编译,而且这些改变是本地化的。
这可能或可能不是一个可能的情况,但我认为这是值得考虑的。
另一个选项是将它们作为非静态方法添加到原始对象上:
即改变:
public class BarUtil { public static Foo transform(Bar toFoo) { ... } }
成
public class Bar { ... public Foo transform() { ...} }
然而在许多情况下,这是不可能的(例如,从XSD / WSDL / etc生成常规的类代码),或者它会使类变得非常长,并且转换方法对于复杂对象通常是一个真正的痛苦,在他们自己的独立class上。 所以是的,我在工具类中有静态方法。
只要在正确的地方使用静态类就可以了。
即:方法是'叶'方法(他们不修改状态,他们只是以某种方式转换input)。 很好的例子就是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,你不能通过查看构造函数来确定必须执行什么初始化步骤。 你必须钻研这个类来find它的依赖关系,并以正确的顺序来初始化事物。 在这种情况下,需要在运行之前初始化ResourceLoader。 现在放大这个依赖性噩梦,你可能会猜测会发生什么。 想象一下,如果没有明确的初始化顺序,试图维护代码。 将它和带有实例的dependency injection进行对比 – 在这种情况下,如果依赖关系没有实现,代码甚至不会编译 !
而且,如果你使用修改状态的静态,就像一个卡片房子。 你永远不知道谁有权访问什么,而且devise往往像一个意大利面条怪物。
最后,同样重要的是,使用静态将程序与特定的实现联系起来。 静态代码是可testing性devise的对立面。 testing静态的代码是一场噩梦。 一个静态调用永远不能换成一个testingdouble(除非你使用专门devise来模拟静态types的testing框架),所以静态系统会导致所有使用它的testing成为一个即时集成testing。
简而言之,对于某些事情来说,静态是很好的,对于小工具或一次性代码我不会阻止它们的使用。 然而,除此之外,它们是可维护性,良好devise和易于testing的血腥噩梦。
这里有一个关于这个问题的好文章: http : //gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/
你被警告远离静态方法的原因是使用它们丧失了对象的优点之一。 对象用于数据封装。 这可以防止意外的副作用发生,从而避免错误。 静态方法没有封装数据*,所以不能获得这种好处。
也就是说,如果你没有使用内部数据,他们可以很好的使用,稍微快一点的执行。 确保你没有触摸全局数据。
- 一些语言也有类级别的variables,这将允许封装数据和静态方法。
这似乎是一个合理的方法。 你不想使用太多的静态类/方法的原因是,你最终将从面向对象的编程转向更多的结构化编程领域。
在你的情况下,你只是简单地把A转换成B,比方说我们正在做的就是把文本转换成为从
"hello" =>(transform)=> "<b>Hello!</b>"
那么静态的方法是有道理的。
然而,如果你经常在一个对象上调用这些静态方法,并且它对于许多调用(例如,你使用它的方式取决于input)往往是唯一的,或者它是对象固有行为的一部分,明智地把它作为对象的一部分并保持它的状态。 一种方法是将其作为接口来实现。
class Interface{ method toHtml(){ return transformed string (eg "<b>Hello!</b>") } method toConsole(){ return transformed string (eg "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 helper方法。 他们创build的HTML元素不受绑定对象的行为,因此是静态的。
编辑2:改变function编程结构化编程(出于某种原因,我感到困惑),道具Torsten指出。
我会认为这是一个devise的气味。 如果你发现自己主要使用静态方法,你可能没有一个很好的面向对象的devise。 这并不一定是坏的,但如同所有的气味,这将使我停下来重新评估。 这暗示你可能会做出更好的面向对象的devise,或者也许你应该走另一个方向,完全避免面向对象的问题。
只要内部状态不起作用,这就好了。 请注意,通常静态方法预计是线程安全的,所以如果使用帮助程序数据结构,请以线程安全的方式使用它们。
我最近重构了一个应用程序来删除/修改一些最初作为静态类实现的类。 随着时间的推移,这些课程获得了很多,人们只是把新function标记为静态的,因为从来没有一个实例浮出水面。
所以,我的答案是,静态类本身并不坏,但现在开始创build实例可能会更容易,然后必须重构。
我曾经在一堆静态方法和一个单例之间来回切换。 两者都解决了这个问题,但是单身人士可以更容易被多个人取代。 (程序员总是很确定,只会有一个东西,我发现自己错了足够的时间来完全放弃静态方法,除非在一些非常有限的情况下)。
无论如何,这个单例让你有能力稍后传递一些东西到工厂去获得一个不同的实例,并且改变整个程序的行为而不会重构。 将一个全局的静态方法类改为具有不同的“支持”数据或稍微不同的行为(子类)的东西是一个主要的困难。
而静态方法没有类似的优势。
所以是的,他们是不好的。
如果这是一种实用的方法,那么把它变成静态是很好的。 番石榴和Apache Commonsbuild立在这个原则上。
我对此的看法纯粹是务实的。 如果这是你的应用程序代码,静态方法通常不是最好的东西。 静态方法有严重的unit testing限制 – 不容易被模拟:你不能在其他testing中注入模拟静态function。 通常也不能将function注入到静态方法中。
所以在我的应用程序逻辑中,我通常有一些小的静态实用程序方法调用。 即
static cutNotNull(String s, int length){ return s == null ? null : s.substring(0, length); }
其中一个好处是我不testing这样的方法:-)
那么,当然没有银弹。 静态类可以用于小公用事业/助手。 但是使用静态方法来进行业务逻辑编程肯定是邪恶的。 考虑下面的代码
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);
它闻起来
- 你没有明确的依赖声明,如果你不深入代码,你可能会忽略你的类和静态方法容器之间的耦合
- 你不能testing从
ItemsProcessor
隔离出来的ItemsProcessor
(大多数testing工具不能模拟静态类),并且它使unit testing变得不可能。 没有unit testing==低质量
如果你知道你永远不需要使用C的内部状态,那很好。 但是,如果将来发生变化,则需要使方法变得非静态。 如果它是非静态的,你可以忽略内部状态,如果你不需要它。
静态方法通常是一个糟糕的select,即使对于无状态的代码。 而是使用这些方法创build一个单例类,并将其注入到想要使用这些方法的类中。 这样的类更容易模拟和testing。 它们更加面向对象。 您可以在需要时使用代理来包装它们。 静力学使得OO变得更加困难,我几乎没有理由在所有情况下使用它们。 不是100%,但几乎全部。