86

说出在决定使用单例还是静态类时的设计注意事项。在这样做的时候,你有点被迫对比两者,所以你能想出的任何对比对于展示你的思维过程也很有用!此外,每个面试官都喜欢看说明性的例子。:)

4

22 回答 22

81
  • 单例可以实现接口并从其他类继承。
  • 单例可以延迟加载。仅在实际需要时。如果初始化包括昂贵的资源加载或数据库连接,那将非常方便。
  • 单例提供了一个实际的对象。
  • 单例可以扩展到工厂。幕后的对象管理是抽象的,因此它更易于维护并产生更好的代码。
于 2008-09-30T00:43:21.857 回答
14

“避免两者”怎么样?单例和静态类:

  • 可以引入全局状态
  • 与多个其他类紧密耦合
  • 隐藏依赖项
  • 会使孤立的单元测试类变得困难

相反,请查看控制容器库的依赖注入和反转。一些 IoC 库将为您处理生命周期管理。

(与往常一样,也有例外,例如静态数学类和 C# 扩展方法。)

于 2010-01-18T18:27:41.120 回答
8

我认为唯一的区别是语法:MySingleton.Current.Whatever() vs MySingleton.Whatever()。正如大卫所说,无论哪种情况,状态最终都是“静态的”。


编辑:埋葬大队从digg过来......无论如何,我想到了一个需要单身人士的案例。静态类不能从基类继承,也不能实现接口(至少在 .Net 中它们不能)。因此,如果您需要此功能,则必须使用单例。

于 2008-09-05T18:51:30.720 回答
7

关于这个问题我最喜欢的讨论之一是在这里(原始站点已关闭,现在链接到Internet Archive Wayback Machine。)

总结一下 Singleton 的灵活性优势:

  • 一个 Singleton 可以很容易地转换成一个工厂
  • 可以轻松修改 Singleton 以返回不同的子类
  • 这可以产生更易于维护的应用程序
于 2008-09-05T23:16:42.143 回答
5

带有大量静态变量的静态类有点难。

/**
 * 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;
       }     

可以对静态类做类似的事情,但在间接调度中会有每次调用的开销。

您可以获取实例并将其作为参数传递给函数。这让代码被定向到“正确的”单例。我们知道你只需要其中一个……直到你不需要。

最大的好处是可以使有状态的单例成为线程安全的,而静态类则不能,除非您将其修改为秘密单例。

于 2008-09-07T23:30:17.850 回答
4

把单例想象成服务。它是一个提供一组特定功能的对象。例如

ObjectFactory.getInstance().makeObject();

对象工厂是执行特定服务的对象。

相比之下,一个充满静态方法的类是您可能想要执行的操作的集合,它们组织在一个相关的组(类)中。例如

StringUtils.reverseString("Hello");
StringUtils.concat("Hello", "World");

此处的 StringUtils 示例是可以在任何地方应用的功能集合。单例工厂对象是一种具有明确职责的特定类型的对象,可以在需要时创建和传递。

于 2008-09-05T22:58:32.360 回答
4

静态类在运行时被实例化。这可能很耗时。只有在需要时才能实例化单例。

于 2008-09-05T23:22:36.670 回答
3

单例不应以与静态类相同的方式使用。本质上,

MyStaticClass.GetInstance().DoSomething();

本质上是一样的

MyStaticClass.DoSomething();

您实际上应该做的是将单例视为另一个对象。如果服务需要单例类型的实例,则在构造函数中传递该实例:

var svc = new MyComplexServce(MyStaticClass.GetInstance());

服务不应该知道该对象是一个单例,并且应该将该对象视为一个对象。

该对象当然可以作为实现细节和整体配置的一个方面来实现,如果这会使事情变得更容易的话,可以作为单例来实现。但是使用对象的东西不应该知道对象是否是单例。

于 2009-03-17T18:50:59.470 回答
2

如果“静态类”是指只有静态变量的类,那么它们实际上可以保持状态。我的理解是,唯一的区别是你如何访问这个东西。例如:

MySingleton().getInstance().doSomething();

相对

MySingleton.doSomething();

MySingleton 的内部结构在它们之间显然会有所不同,但是,抛开线程安全问题不谈,它们在客户端代码方面都将执行相同的操作。

于 2008-09-05T18:52:58.313 回答
2

单例模式通常用于服务实例独立或静态数据,其中多个线程可以同时访问数据。一个例子可以是州代码。

于 2010-11-24T07:25:44.710 回答
1

永远不应使用单例(除非您将没有可变状态的类视为单例)。“静态类”应该没有可变状态,除了线程安全的缓存等。

几乎所有单例示例都显示了如何不这样做。

于 2008-09-05T18:54:48.387 回答
0

如果一个单例是你可以处理的,要在它之后清理它,你可以考虑它是一个有限的资源(即只有一个),你并不总是需要它,并且有某种内存或分配时的资源成本。

当你有一个单例时,清理代码看起来更自然,而不是包含静态字段的静态类。

但是,无论哪种方式,代码看起来都一样,因此如果您有更具体的询问理由,也许您应该详细说明。

于 2008-09-05T18:58:14.853 回答
0

两者可能非常相似,但请记住,真正的 Singleton 必须自己实例化(授予一次)然后提供服务。返回一个实例的 PHP 数据库类mysqli并不是真正的单例(有些人称之为),因为它返回的是另一个类的实例,而不是具有该实例作为静态成员的类的实例。

因此,如果您正在编写一个计划在代码中只允许一个实例的新类,那么您不妨将其编写为 Singleton。将其视为编写一个简单的 jane 类并添加到它以促进单实例化要求。如果您正在使用其他人的无法修改的类(如mysqli),则应该使用静态类(即使您未能在其定义前加上关键字)。

于 2008-09-05T18:59:37.160 回答
0

单例更灵活,这在您希望 Instance 方法根据某些上下文返回单例类型的不同具体子类的情况下很有用。

于 2008-09-05T19:05:29.143 回答
0

静态类不能作为参数传递;单例的实例即可。如其他答案中所述,请注意静态类的线程问题。

rp

于 2008-09-05T19:10:01.283 回答
0

单例可能有构造函数和析构函数。根据您的语言,第一次使用单例时可能会自动调用构造函数,如果根本不使用单例,则不会调用。静态类不会有这样的自动初始化。

一旦获得对单例对象的引用,就可以像使用任何其他对象一样使用它。如果之前存储了对单例的引用,则客户端代码甚至可能不需要知道它使用单例:

Foo foo = Foo.getInstance();
doSomeWork(foo); // doSomeWork wont even know Foo is a singleton

当您选择放弃单例模式以支持真正的模式(如 IoC)时,这显然会使事情变得更容易。

于 2008-09-05T22:50:21.303 回答
0

当您需要在运行时计算一些可能会在编译时计算的东西时,请使用单例模式,例如查找表。

于 2008-09-05T22:58:12.517 回答
0

我认为 Singleton 比静态类更有意义的地方是当您必须构建一个昂贵的资源池(如数据库连接)时。如果没有人使用它们,您将不会对创建池感兴趣(静态类将意味着您在加载类时执行昂贵的工作)。

于 2008-09-07T21:19:29.740 回答
0

如果您想强制有效缓存数据,单例也是一个好主意。例如,我有一个在 xml 文档中查找定义的类。由于解析文档可能需要一段时间,因此我设置了定义缓存(我使用 SoftReferences 来避免 outOfmemeoryErrors)。如果所需的定义不在缓存中,我会进行昂贵的 xml 解析。否则我从缓存中返回一个副本。由于拥有多个缓存意味着我仍然可能需要多次加载相同的定义,因此我需要一个静态缓存。我选择将这个类实现为单例,这样我就可以只使用普通(非静态)数据成员来编写这个类。如果出于某种原因(序列化、单元测试等)需要它,这使我仍然可以创建类的实例化。

于 2009-03-17T19:14:22.240 回答
0

如前所述,Singleton 就像一个服务。Pro 是它的灵活性。静态的,好吧,你需要一些静态部分来实现单例。

Singleton 有代码来处理实际对象的实例化,如果您遇到赛车问题,这可能会很有帮助。在静态解决方案中,您可能需要在多个代码位置处理赛车问题。

但是,与可以用一些静态变量构造单例一样,您可以将其与 'goto' 进行比较。它对于构建其他结构可能非常有用,但您确实需要知道如何使用它并且不应该“过度使用”它。因此,一般建议是坚持使用 Singleton,并在必要时使用静态。

另请查看另一篇文章:为什么选择静态类而不是单例实现?

于 2013-04-08T13:28:58.130 回答
0

参考这个

概括:

一种。您可以遵循的一个简单的经验法则是,如果它不需要维护状态,您可以使用静态类,否则您应该使用单例。

湾。如果它是一个特别“重”的对象,则使用 Singleton。如果您的对象很大并且占用了合理的内存量,那么很多 n/w 调用(连接池)..等等。确保它不会被多次实例化。单例类将有助于防止这种情况发生

于 2015-07-19T16:03:10.860 回答
-1

当单个类需要状态时。单例维护一个全局状态,静态类没有。

例如,围绕注册表类创建一个助手:如果您有可更改的配置单元(HKey Current User vs. HKEY Local Machine),您可以去:

RegistryEditor editor = RegistryEditor.GetInstance();
editor.Hive = LocalMachine

现在,对该单例的任何进一步调用都将在本地机器配置单元上运行。否则,使用静态类,您将不得不指定本地机器配置单元 everytiem,或者使用类似ReadSubkeyFromLocalMachine.

于 2008-09-05T18:48:30.837 回答