在决定使用单例还是静态类时,请列出设计注意事项。在执行此操作时,您被迫将两者进行对比,因此可以提出的任何对比也都有助于显示您的思维过程!此外,每个面试官都喜欢看示例性例子。:)
Answers:
Hide Dependencies
关于这一点的小补充:您可以通过使用Ninject例如实现控制反转模式。这允许您通过将类型的一个实例绑定SingletonScope
到来仅使用它的一个实例,这意味着它将总是注入相同的实例,但该类不会是单例的。
关于此问题,我最喜欢的讨论之一是在此(原始站点关闭,现在链接到Internet Archive Wayback Machine。)
总结Singleton的灵活性优势:
带有大量静态变量的静态类有点麻烦。
/**
* Grotty static semaphore
**/
public static class Ugly {
private static int count;
public synchronized static void increment(){
count++;
}
public synchronized static void decrement(){
count--;
if( count<0 ) {
count=0;
}
}
public synchronized static boolean isClear(){
return count==0;
}
}
具有实际实例的单例更好。
/**
* Grotty static semaphore
**/
public static class LessUgly {
private static LessUgly instance;
private int count;
private LessUgly(){
}
public static synchronized getInstance(){
if( instance==null){
instance = new LessUgly();
}
return instance;
}
public synchronized void increment(){
count++;
}
public synchronized void decrement(){
count--;
if( count<0 ) {
count=0;
}
}
public synchronized boolean isClear(){
return count==0;
}
}
该状态仅在实例中处于。
因此,可以稍后修改单例以进行池化,线程本地实例等。并且无需更改任何已编写的代码即可获得好处。
public static class LessUgly {
private static Hashtable<String,LessUgly> session;
private static FIFO<LessUgly> freePool = new FIFO<LessUgly>();
private static final POOL_SIZE=5;
private int count;
private LessUgly(){
}
public static synchronized getInstance(){
if( session==null){
session = new Hashtable<String,LessUgly>(POOL_SIZE);
for( int i=0; i < POOL_SIZE; i++){
LessUgly instance = new LessUgly();
freePool.add( instance)
}
}
LessUgly instance = session.get( Session.getSessionID());
if( instance == null){
instance = freePool.read();
}
if( instance==null){
// TODO search sessions for expired ones. Return spares to the freePool.
//FIXME took too long to write example in blog editor.
}
return instance;
}
可以对静态类进行类似的操作,但是在间接分派中会存在每次调用的开销。
您可以获取实例并将其作为参数传递给函数。这样就可以将代码定向到“正确的”单例。我们知道您只需要其中之一...直到您不需要。
最大的好处是可以使有状态的单例成为线程安全的,而静态类则不能,除非您将其修改为秘密的单例。
静态类在运行时实例化。这可能很耗时。仅在需要时才能实例化单例。
单例不应该以与静态类相同的方式使用。本质上,
MyStaticClass.GetInstance().DoSomething();
基本上与
MyStaticClass.DoSomething();
实际上,您应该将单例视为另一个对象。如果服务需要单例类型的实例,则在构造函数中传递该实例:
var svc = new MyComplexServce(MyStaticClass.GetInstance());
服务不应意识到该对象是单例对象,应将该对象视为一个对象。
当然,可以将对象实现为实现细节和整体配置的一个方面,如果使事情变得更简单,则可以实现为单例。但是使用该对象的事物不必知道该对象是否为单例。
如果用“静态类”表示仅具有静态变量的类,则它们实际上可以维护状态。我的理解是,唯一的区别就是您访问该内容的方式。例如:
MySingleton().getInstance().doSomething();
与
MySingleton.doSomething();
MySingleton的内部结构之间显然会有所不同,但是,除了线程安全性问题,它们在客户端代码方面都将执行相同的操作。
永远不要使用单例(除非您将没有可变状态的类视为单例)。“静态类”应该没有可变状态,除了线程安全的高速缓存之类。
几乎任何一个单例示例都说明了如何不这样做。
单例可能具有构造函数和析构函数。根据您的语言,第一次使用您的单例时,可能会自动调用该构造函数;如果根本不使用您的单例,则永远不会调用该构造函数。静态类将没有这种自动初始化。
一旦获得对单例对象的引用,就可以像其他任何对象一样使用它。如果对单例的引用存储在更早的位置,则客户端代码甚至可能不需要使用单例来了解其代码:
Foo foo = Foo.getInstance();
doSomeWork(foo); // doSomeWork wont even know Foo is a singleton
当您选择抛弃Singleton模式以使用IoC之类的真实模式时,这显然使事情变得容易。
当您需要在运行时计算某些内容时,请使用单例模式,如果可以的话,这些内容应在编译时进行计算,例如查找表。
如果您要强制高效缓存数据,则单例也是一个好主意。例如,我有一个在xml文档中查找定义的类。由于解析文档可能需要一段时间,因此我设置了一个定义缓存(我使用SoftReferences来避免outOfmemeoryErrors)。如果所需的定义不在高速缓存中,则执行昂贵的xml解析。否则,我将从缓存中返回一个副本。由于具有多个缓存将意味着我仍然可能必须多次加载相同的定义,因此我需要具有静态缓存。我选择将该类实现为单例,以便仅使用常规(非静态)数据成员编写该类。这使我仍然可以出于某种原因(序列化,单元测试等)使用该类的一个实例。
如前所述,Singleton就像一项服务。专业是它的灵活性。静态的,那么,您需要一些静态零件才能实现Singleton。
Singleton有代码来照顾实际对象的实例化,如果遇到赛车问题,这可能是一个很大的帮助。在静态解决方案中,您可能需要在多个代码位置处理竞速问题。
但是,与可以使用一些静态变量构造Singleton一样,您可以将其与“ goto”进行比较。它对于构建其他结构可能非常有用,但是您确实需要知道如何使用它,而不应该“过度使用”它。因此,一般的建议是坚持使用Singleton,并在必要时使用static。
还检查另一篇文章:为什么选择单例实现而不是静态类?
当单个类需要状态时。单例保持全局状态,静态类不保持全局状态。
例如,为注册表类提供帮助:如果您拥有可更改的配置单元(HKey当前用户与HKEY本地计算机),则可以执行以下操作:
RegistryEditor editor = RegistryEditor.GetInstance();
editor.Hive = LocalMachine
现在,对该单例的任何进一步调用将在Local Machine配置单元上运行。否则,使用静态类,您将必须指定Local Machine配置所有内容,或使用类似的方法ReadSubkeyFromLocalMachine
。