Is using a lot of static methods a bad thing?
当类不需要跟踪内部状态时,我倾向于将类中的所有方法声明为静态的。例如,如果我需要将a转换为b,并且不依赖于可能会变化的一些内部状态c,那么我创建一个静态转换。如果有一个内部状态C我想能够调整,那么我添加了一个构造函数来设置C,并且不使用静态转换。
我阅读了各种建议(包括StackOverflow),不要过度使用静态方法,但我仍然无法理解上面的经验法则有什么问题。
这是不是一个合理的方法?
有两种常见的静态方法:
- "安全"静态方法总是为相同的输入提供相同的输出。它不修改全局,也不调用任何类的任何"不安全"静态方法。本质上,您使用的是一种有限的函数式编程——不要害怕这些,它们很好。
- 一个"不安全"的静态方法会改变全局状态、全局对象的代理或其他一些不可测试的行为。这些都是程序化编程的基础,如果可能的话应该重构。
"不安全"静态有几个常见的用法——例如,在单例模式中——但是请注意,尽管您称它们为漂亮的名称,但您只是在改变全局变量。在使用不安全的静电剂之前要仔细考虑。
没有任何内部状态的对象是可疑的。
通常,对象封装状态和行为。只封装行为的对象是奇数的。有时它是一个轻量或轻量级的例子。
其他时候,它是用对象语言完成的程序设计。
这真的只是约翰米利金伟大答案的后续行动。
尽管可以安全地将无状态方法(相当多的函数)设置为静态,但有时会导致难以修改的耦合。假设您有一个静态方法,例如:
1 2 3 | public class StaticClassVersionOne { public static void doSomeFunkyThing(int arg); } |
你称之为:
1 | StaticClassVersionOne.doSomeFunkyThing(42); |
这一切都很好,而且非常方便,直到您遇到一个必须修改静态方法行为的情况,并且发现您与
但是考虑一下,如果您已经创建了一个接口来提供该方法,并将其提供给调用方,那么现在当行为必须更改时,可以创建一个新的类来实现该接口,该接口更干净、更容易测试和更可维护,而该类是提供给调用方的。在这个场景中,调用类不需要修改甚至重新编译,并且更改是本地化的。
这可能是或不是一个可能的情况,但我认为这是值得考虑的。
另一种选择是将它们作为非静态方法添加到原始对象上:
即:改变:
1 2 3 | public class BarUtil { public static Foo transform(Bar toFoo) { ... } } |
进入之内
1 2 3 4 | public class Bar { ... public Foo transform() { ...} } |
然而,在许多情况下,这是不可能的(例如,从xsd/wsdl/etc生成常规类代码),否则它将使类非常长,而转换方法对于复杂对象来说通常是一种真正的痛苦,您只需要将它们放在自己的类中即可。所以是的,我在实用程序类中有静态方法。
静态类只要在正确的地方使用就可以。
即:"叶"方法(它们不修改状态,只是以某种方式转换输入)。例如path.combine。这些东西对terser语法很有用。
我对静态的问题有很多:
首先,如果您有静态类,依赖项是隐藏的。考虑以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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-deadise-managers-and-context/
警告您不要使用静态方法的原因是,使用静态方法会丧失对象的一个优点。对象用于数据封装。这可以防止意外的副作用发生,从而避免错误。静态方法没有封装的数据*,因此不能获得这种好处。
也就是说,如果您不使用内部数据,它们可以使用,并且执行速度稍快。不过,请确保不要接触其中的全局数据。
- 有些语言还具有类级变量,允许封装数据和静态方法。
这似乎是一个合理的方法。您不想使用太多静态类/方法的原因是,您最终会从面向对象的编程转向结构化编程领域。
在您的情况下,如果您只是将a转换为b,那么我们所要做的就是将文本转换为
1 | "hello" =>(transform)=>"Hello!" |
那么静态方法就有意义了。
但是,如果您经常在一个对象上调用这些静态方法,并且它对于许多调用都是唯一的(例如,您使用它的方式取决于输入),或者它是对象固有行为的一部分,那么将它作为对象的一部分并保持其状态是明智的。实现这一点的一种方法是将其实现为一个接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Interface{ method toHtml(){ return transformed string (e.g."Hello!") } 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提供指出这一点的道具。
我最近重构了一个应用程序,以删除/修改一些最初作为静态类实现的类。随着时间的推移,这些类获得了很多,人们只是不断地将新函数标记为静态的,因为从来没有实例在周围浮动。
所以,我的答案是静态类本身并不坏,但是现在开始创建实例,然后稍后重构可能会更容易。
我认为它是一种设计气味。如果你发现自己使用的大多是静态方法,你可能没有很好的面向对象设计。它不一定是坏的,但是和所有的气味一样,它会让我停下来重新评估。它暗示你可以做一个更好的面向对象的设计,或者你应该走另一个方向,避免OO完全解决这个问题。
我以前经常在一个有很多静态方法的类和一个单例类之间来回走动。两者都解决了这个问题,但是单例可以更容易地替换为多个。(程序员似乎总是很确定只有一种东西,我发现自己错了足够多的时间完全放弃静态方法,除了在一些非常有限的情况下)。
无论如何,singleton使您能够稍后将某个东西传递到工厂以获得不同的实例,从而在不重构的情况下改变整个程序的行为。将静态方法的全局类更改为具有不同的"支持"数据或稍有不同的行为(子类)的类是一个大麻烦。
静态方法也没有类似的优势。
所以是的,他们很坏。
当然,没有银弹。静态类对于小实用程序/帮助程序来说是可以的。但是使用静态方法进行业务逻辑编程当然是邪恶的。考虑以下代码
1 2 3 4 5 6 7 8 9 10 11 | 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; } } |
您会看到对
- 您没有声明显式依赖项,如果不深入研究代码,可能会忽略类和静态方法容器之间的耦合。
- 你不能测试
BusinessService 将它与ItemsProcessor 隔离(大多数测试工具不模拟静态类),这使得单元测试成为不可能。无单元测试==低质量
如果它是一个实用方法,最好使其静态化。瓜娃和阿帕奇公地是建立在这一原则之上的。
我对此的看法纯粹是务实的。如果是你的应用程序代码,静态方法通常不是最好的选择。静态方法有严重的单元测试限制——它们不容易被模拟:不能将模拟的静态功能注入到其他测试中。通常也不能将功能注入静态方法。
所以在我的应用程序逻辑中,我通常有一些小的静态实用程序,比如方法调用。即。
1 2 3 | static cutNotNull(String s, int length){ return s == null ? null : s.substring(0, length); } |
其中一个好处是我没有测试这种方法:—)
如果你知道你永远不需要使用C的内部状态,那就好了。不过,如果将来这种情况发生变化,您需要使方法非静态。如果开始时它是非静态的,那么如果不需要它,您可以忽略内部状态。
只要不是内部状态开始起作用,就可以了。请注意,通常静态方法是线程安全的,因此如果使用助手数据结构,请以线程安全的方式使用它们。
静态方法通常是一个错误的选择,即使对于无状态代码也是如此。相反,用这些方法创建一个单例类,这些方法被实例化一次并注入那些想要使用这些方法的类中。这样的类更容易被模拟和测试。它们更面向对象。您可以在需要时用代理包装它们。静态使OO变得更难,我认为几乎在所有情况下都没有理由使用它们。不是100%,而是几乎全部。