说出在决定使用单例还是静态类时的设计注意事项。在这样做的时候,你有点被迫对比两者,所以你能想出的任何对比对于展示你的思维过程也很有用!此外,每个面试官都喜欢看说明性的例子。:)
22 回答
- 单例可以实现接口并从其他类继承。
- 单例可以延迟加载。仅在实际需要时。如果初始化包括昂贵的资源加载或数据库连接,那将非常方便。
- 单例提供了一个实际的对象。
- 单例可以扩展到工厂。幕后的对象管理是抽象的,因此它更易于维护并产生更好的代码。
我认为唯一的区别是语法:MySingleton.Current.Whatever() vs MySingleton.Whatever()。正如大卫所说,无论哪种情况,状态最终都是“静态的”。
编辑:埋葬大队从digg过来......无论如何,我想到了一个需要单身人士的案例。静态类不能从基类继承,也不能实现接口(至少在 .Net 中它们不能)。因此,如果您需要此功能,则必须使用单例。
关于这个问题我最喜欢的讨论之一是在这里(原始站点已关闭,现在链接到Internet Archive Wayback Machine。)
总结一下 Singleton 的灵活性优势:
- 一个 Singleton 可以很容易地转换成一个工厂
- 可以轻松修改 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;
}
可以对静态类做类似的事情,但在间接调度中会有每次调用的开销。
您可以获取实例并将其作为参数传递给函数。这让代码被定向到“正确的”单例。我们知道你只需要其中一个……直到你不需要。
最大的好处是可以使有状态的单例成为线程安全的,而静态类则不能,除非您将其修改为秘密单例。
把单例想象成服务。它是一个提供一组特定功能的对象。例如
ObjectFactory.getInstance().makeObject();
对象工厂是执行特定服务的对象。
相比之下,一个充满静态方法的类是您可能想要执行的操作的集合,它们组织在一个相关的组(类)中。例如
StringUtils.reverseString("Hello");
StringUtils.concat("Hello", "World");
此处的 StringUtils 示例是可以在任何地方应用的功能集合。单例工厂对象是一种具有明确职责的特定类型的对象,可以在需要时创建和传递。
静态类在运行时被实例化。这可能很耗时。只有在需要时才能实例化单例。
单例不应以与静态类相同的方式使用。本质上,
MyStaticClass.GetInstance().DoSomething();
本质上是一样的
MyStaticClass.DoSomething();
您实际上应该做的是将单例视为另一个对象。如果服务需要单例类型的实例,则在构造函数中传递该实例:
var svc = new MyComplexServce(MyStaticClass.GetInstance());
服务不应该知道该对象是一个单例,并且应该将该对象视为一个对象。
该对象当然可以作为实现细节和整体配置的一个方面来实现,如果这会使事情变得更容易的话,可以作为单例来实现。但是使用对象的东西不应该知道对象是否是单例。
如果“静态类”是指只有静态变量的类,那么它们实际上可以保持状态。我的理解是,唯一的区别是你如何访问这个东西。例如:
MySingleton().getInstance().doSomething();
相对
MySingleton.doSomething();
MySingleton 的内部结构在它们之间显然会有所不同,但是,抛开线程安全问题不谈,它们在客户端代码方面都将执行相同的操作。
单例模式通常用于服务实例独立或静态数据,其中多个线程可以同时访问数据。一个例子可以是州代码。
永远不应使用单例(除非您将没有可变状态的类视为单例)。“静态类”应该没有可变状态,除了线程安全的缓存等。
几乎所有单例示例都显示了如何不这样做。
如果一个单例是你可以处理的,要在它之后清理它,你可以考虑它是一个有限的资源(即只有一个),你并不总是需要它,并且有某种内存或分配时的资源成本。
当你有一个单例时,清理代码看起来更自然,而不是包含静态字段的静态类。
但是,无论哪种方式,代码看起来都一样,因此如果您有更具体的询问理由,也许您应该详细说明。
两者可能非常相似,但请记住,真正的 Singleton 必须自己实例化(授予一次)然后提供服务。返回一个实例的 PHP 数据库类mysqli
并不是真正的单例(有些人称之为),因为它返回的是另一个类的实例,而不是具有该实例作为静态成员的类的实例。
因此,如果您正在编写一个计划在代码中只允许一个实例的新类,那么您不妨将其编写为 Singleton。将其视为编写一个简单的 jane 类并添加到它以促进单实例化要求。如果您正在使用其他人的无法修改的类(如mysqli
),则应该使用静态类(即使您未能在其定义前加上关键字)。
单例更灵活,这在您希望 Instance 方法根据某些上下文返回单例类型的不同具体子类的情况下很有用。
静态类不能作为参数传递;单例的实例即可。如其他答案中所述,请注意静态类的线程问题。
rp
单例可能有构造函数和析构函数。根据您的语言,第一次使用单例时可能会自动调用构造函数,如果根本不使用单例,则不会调用。静态类不会有这样的自动初始化。
一旦获得对单例对象的引用,就可以像使用任何其他对象一样使用它。如果之前存储了对单例的引用,则客户端代码甚至可能不需要知道它使用单例:
Foo foo = Foo.getInstance();
doSomeWork(foo); // doSomeWork wont even know Foo is a singleton
当您选择放弃单例模式以支持真正的模式(如 IoC)时,这显然会使事情变得更容易。
当您需要在运行时计算一些可能会在编译时计算的东西时,请使用单例模式,例如查找表。
我认为 Singleton 比静态类更有意义的地方是当您必须构建一个昂贵的资源池(如数据库连接)时。如果没有人使用它们,您将不会对创建池感兴趣(静态类将意味着您在加载类时执行昂贵的工作)。
如果您想强制有效缓存数据,单例也是一个好主意。例如,我有一个在 xml 文档中查找定义的类。由于解析文档可能需要一段时间,因此我设置了定义缓存(我使用 SoftReferences 来避免 outOfmemeoryErrors)。如果所需的定义不在缓存中,我会进行昂贵的 xml 解析。否则我从缓存中返回一个副本。由于拥有多个缓存意味着我仍然可能需要多次加载相同的定义,因此我需要一个静态缓存。我选择将这个类实现为单例,这样我就可以只使用普通(非静态)数据成员来编写这个类。如果出于某种原因(序列化、单元测试等)需要它,这使我仍然可以创建类的实例化。
如前所述,Singleton 就像一个服务。Pro 是它的灵活性。静态的,好吧,你需要一些静态部分来实现单例。
Singleton 有代码来处理实际对象的实例化,如果您遇到赛车问题,这可能会很有帮助。在静态解决方案中,您可能需要在多个代码位置处理赛车问题。
但是,与可以用一些静态变量构造单例一样,您可以将其与 'goto' 进行比较。它对于构建其他结构可能非常有用,但您确实需要知道如何使用它并且不应该“过度使用”它。因此,一般建议是坚持使用 Singleton,并在必要时使用静态。
参考这个
概括:
一种。您可以遵循的一个简单的经验法则是,如果它不需要维护状态,您可以使用静态类,否则您应该使用单例。
湾。如果它是一个特别“重”的对象,则使用 Singleton。如果您的对象很大并且占用了合理的内存量,那么很多 n/w 调用(连接池)..等等。确保它不会被多次实例化。单例类将有助于防止这种情况发生
当单个类需要状态时。单例维护一个全局状态,静态类没有。
例如,围绕注册表类创建一个助手:如果您有可更改的配置单元(HKey Current User vs. HKEY Local Machine),您可以去:
RegistryEditor editor = RegistryEditor.GetInstance();
editor.Hive = LocalMachine
现在,对该单例的任何进一步调用都将在本地机器配置单元上运行。否则,使用静态类,您将不得不指定本地机器配置单元 everytiem,或者使用类似ReadSubkeyFromLocalMachine
.