037、单例模式 VS 静态方法

Par @Martin dans le
Tags :

转载自: 程序设计之 — 单例模式 VS 静态方法

我们在设计程序经常会有这种需求, 某个类里的方法能够全局访问, 在这种情况下有两种实现方案:

  • 单例模式 (Singleton)
  • 静态方法

但是, 对于这两种实现方式, 那种更好呢?

在国内论坛上看了一下其他的一些看法

关于这个问题, 下面是一些同仁的观点:

观点一: (单例)

单例模式比静态方法有很多优势:

  • 首先, 单例可以继承类, 实现接口, 而静态类不能 (可以集成类, 但不能集成实例成员) ;
  • 其次, 单例可以被延迟初始化, 静态类一般在第一次加载是初始化;
  • 再次, 单例类可以被集成, 他的方法可以被覆写;
  • 最后, 或许最重要的是, 单例类可以被用于多态而无需强迫用户只假定唯一的实例.

举个例子, 你可能在开始时只写一个配置, 但是以后你可能需要支持超过一个配置集, 或者可能需要允许用户从外部从外部文件中加载一个配置对象, 或者编写自己的. 你的代码不需要关注全局的状态, 因此你的代码会更加灵活.

观点二: (静态方法)

静态方法中产生的对象, 会随着静态方法执行完毕而释放掉, 而且执行类中的静态方法时, 不会实例化静态方法所在的类. 如果是用 singleton, 产生的那一个唯一的实例, 会一直在内存中, 不会被 GC 清除的 (原因是静态的属性变量不会被 GC 清除), 除非整个 JVM 退出了.

观点三: (Good!)

由于 DAO 的初始化, 会比较占系统资源的, 如果用静态方法来取, 会不断地初始化和释放, 所以我个人认为如果不存在比较复杂的事务管理, 用 singleton 会比较好.

总结:

大家对这个问题都有一个共识: 那就是实例化方法更多被使用和稳妥, 静态方法少使用.

误解

有时候我们对静态方法和实例化方法会有一些误解.

大家都以为”静态方法常驻内存, 实例方法不是, 所以静态方法效率高但占内存.”

事实上, 他们都是一样的, 在加载时机和占用内存上, 静态方法和实例方法是一样的, 在类型第一次被使用时加载. 调用的速度基本上没有差别.

大家都以为”静态方法在堆上分配内存, 实例方法在堆栈上”

事实上所有的方法都不可能在堆或者堆栈上分配内存, 方法作为代码是被加载到特殊的代码内存区域, 这个内存区域是不可写的. 方法占不占用更多内存, 和它是不是static没什么关系.

因为字段是用来存储每个实例对象的信息的, 所以字段会占有内存, 并且因为每个实例对象的状态都不一致 (至少不能认为它们是一致的) , 所以每个实例对象的所以字段都会在内存中有一分拷贝, 也因为这样你才能用它们来区分你现在操作的是哪个对象.

但方法不一样, 不论有多少个实例对象, 它的方法的代码都是一样的, 所以只要有一份代码就够了. 因此无论是static还是non-static的方法, 都只存在一份代码, 也就是只占用一份内存空间.

同样的代码, 为什么运行起来表现却不一样?这就依赖于方法所用的数据了. 主要有两种数据来源, 一种就是通过方法的参数传进来, 另一种就是使用class的成员变量的值……

大家都以为”实例方法需要先创建实例才可以调用, 比较麻烦, 静态方法不用, 比较简单”

事实上如果一个方法与他所在类的实例对象无关, 那么它就应该是静态的, 而不应该把它写成实例方法, 所以所有的实例方法都与实例有关, 既然与实例有关, 那么创建实例就是必然的步骤, 没有麻烦简单一说.

当然你完全可以把所有的实例方法都写成静态的, 将实例作为参数传入即可, 一般情况下可能不会出什么问题.

从面向对象的角度上来说, 在抉择使用实例化方法或静态方法时, 应该根据是否该方法和实例化对象具有逻辑上的相关性, 如果是就应该使用实例化对象 反之使用静态方法. 这只是从面向对象角度上来说的.

如果从线程安全、性能、兼容性上来看 也是选用实例化方法为宜.

我们为什么要把方法区分为: 静态方法和实例化方法?

如果我们继续深入研究的话, 就要脱离技术谈理论了.

早期的结构化编程, 几乎所有的方法都是”静态方法”, 引入实例化方法概念是面向对象概念出现以后的事情了, 区分静态方法和实例化方法不能单单从性能上去理解, 创建 c++,Java,c# 这样面向对象语言的大师引入实例化方法一定不是要解决什么性能、内存的问题, 而是为了让开发更加模式化、面向对象化. 这样说的话, 静态方法和实例化方式的区分是为了解决模式的问题.

拿别人一个例子说事:
比如说”人”这个类, 每个人都有姓名、年龄、性别、身高等, 这些属性就应该是非静态的, 因为每个人都的这些属性都不相同; 但人在生物学上属于哪个门哪个纲哪个目等, 这个属性是属于整个人类, 所以就应该是静态的——它不依赖与某个特定的人, 不会有某个人是”脊椎动物门哺乳动物纲灵长目”而某个人却是”偶蹄目”的

在国外一些论坛的看法(重点)

什么时候使用静态类代替 singleton

这里有几个很好的静态类比singleton更好的应用场景. 最基本的例子就是在 Java 中的 java.lang.Math 类的实现方式, Math 类就是用过静态方法来实现的,而不是单例来实现的.

总结:
如果你的 singleton 不提维持任何状态, 仅仅是提供全局的访问, 这个时候就适合用静态类, 这样速度也更快, 因为 static bind 在编译期间 (compile during). 记住不经意维持子类的状态, 尤其是在并发的情况下, 多个线程并发修改,这容易导致不容易发现的 race condition 关于 race condition.

静态类适用于一些工具类, 其他的如单个访问资源就可以用 singleton.

静态类和 singleton 之间的区别

  • static 类有更好的访问效率 (Static class provides better performance than Singleton pattern, because static methods are bonded on compile time)
  • singleton 比 static class 更容易测试, 那个容易模拟 (mock), 哪个就容易测试, singleton 很容易用 JUnit测试, 因为你能够传递 mock 对象, 当 singleton 需要的时候 (作为方法参数或者构造函数参数)
  • 如果你的需求是维护 (maintain) 状态, 那么 singleton 比 static class 更好, 如果使用 static class 会出现一些问题
  • singleton 支持延迟加载, 而 static class 则不支持这样的特性, 在重量级的对象, 延迟加载就显得非常重要
  • 在一些依赖注入 (Dependency injection framework) 的框架, 它能够很好的管理 singleton 对象, 例如 spring

singleton 相对于静态类的一些高级特点

singleton 对于 static class 主要的优点是更加面向对象.

对于 singleton 你可以使用继承 (Inheritance) 和多态 (polymorphism) 来继承一个基类, 实现一个接口, 提供不同功能 的实现.

例如, Java 中 java.lang.Runtime, 该类就是一个 singleton 的, 调用 getRuntime(), 基于不同的 JVM,返回不同的实现对象, 针对一个一个 JVM,确保只有一个 Runtime 对象, 如果使用 static class 就不能很好的来实现这样的功能了.