什么时候应该使用单例模式而不是静态类?

时间:2008-09-05 18:48:04

标签: design-patterns

在决定使用singleton与静态类之间命名设计注意事项。在这样做时,你有点被迫对比这两者,所以无论你能想出什么对比,都有助于展示你的思考过程!此外,每位采访者都喜欢看到说明性的例子。 :)

22 个答案:

答案 0 :(得分:80)

  • 单身人士可以实现接口并从其他类继承。
  • 单身人士可以懒惰加载。只有在实际需要的时候。如果初始化包括昂贵的资源加载或数据库连接,这非常方便。
  • 单身人士提供了一个实际的对象。
  • 单身人士可以延伸到工厂。幕后的对象管理是抽象的,因此它可以更好地维护并产生更好的代码。

答案 1 :(得分:13)

“避免两者”怎么样?单身人士和静态班级:

  • 可能会介绍全球状态
  • 紧密耦合到其他多个班级
  • 隐藏依赖关系
  • 可以使单元测试类难以隔离

相反,请查看Dependency InjectionInversion of Control Container个库。一些IoC库将为您处理生命周期管理。

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

答案 2 :(得分:8)

我认为唯一的区别是语法:MySingleton.Current.Whatever()vs MySingleton.Whatever()。正如大卫所说,国家在任何一种情况下最终都是“静态的”。


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

答案 3 :(得分:7)

我最喜欢的关于此问题的讨论之一是here(原始网站已关闭,现已链接到Internet Archive Wayback Machine。)

总结Singleton的灵活性优势:

  • 单身人士可以轻松转换 进入工厂
  • 单身人士很容易 修改后返回不同 子类
  • 这可以带来更易维护的应用程序

答案 4 :(得分: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;
       }     

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

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

最大的好处是有状态的单身人士可以成为线程安全的,而静态的单人则不能,除非你把它修改成一个秘密的单身人士。

答案 5 :(得分:4)

将单身人士视为服务。它是一个提供特定功能集的对象。 E.g。

ObjectFactory.getInstance().makeObject();

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

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

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

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

答案 6 :(得分:4)

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

MyStaticClass.GetInstance().DoSomething();

基本相同
MyStaticClass.DoSomething();

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

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

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

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

答案 7 :(得分:4)

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

答案 8 :(得分:2)

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

答案 9 :(得分:2)

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

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

MySingleton.doSomething();

MySingleton的内部显然会在它们之间有所不同,但除了线程安全问题之外,它们在客户端代码方面都会执行相同的操作。

答案 10 :(得分:1)

不应该使用单身人士(除非你认为一个没有可变状态的类是单身人士)。除了线程安全的缓存之外,“静态类”应该没有可变状态。

几乎所有单身人士的例子都显示了如何不这样做。

答案 11 :(得分:0)

我认为Singleton比静态类更有意义的地方是你需要构建一个代价高昂的资源池(比如数据库连接)。如果没有人使用它们,你就不会对创建池感兴趣(静态类意味着你在加载类时会做昂贵的工作)。

答案 12 :(得分:0)

参考this

摘要:

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

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

答案 13 :(得分:0)

如上所述,Singleton就像一项服务。专业是它的灵活性。 静态,你需要一些静态部分才能实现Singleton。

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

然而,与Singleton相同可以使用一些静态变量构建,您可以将它与'goto'进行比较。它对于构建其他结构非常有用,但你真的需要知道如何使用它,不应该“过度使用”它。因此,一般建议坚持使用Singleton,如果必须,请使用static。

另请查看其他帖子:Why choose a static class over a singleton implementation?

答案 14 :(得分:0)

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

答案 15 :(得分:0)

如果一个单身是你可以处理的东西,为了在它之后进行清理,你可以在它是一个有限的资源(即只有一个)你不需要的时候考虑它,并且有一些分配时的内存或资源成本。

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

然而,代码看起来都是一样的,所以如果你有更具体的理由要求,也许你应该详细说明。

答案 16 :(得分:0)

如果需要在运行时计算某些内容,可以在编译时使用单例模式(如果可以的话),例如查找表。

答案 17 :(得分:0)

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

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

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

当你选择放弃Singleton模式以支持像IoC这样的真实模式时,这显然会让事情变得更容易。

答案 18 :(得分:0)

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

RP

答案 19 :(得分:0)

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

答案 20 :(得分:0)

两者可能非常相似,但请记住,真正的Singleton必须本身实例化(授予,一次)然后提供。返回mysqli实例的PHP数据库类实际上并不是Singleton(正如有些人所称的那样),因为它返回另一个类的实例,而不是将实例作为静态的类的实例构件。

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

答案 21 :(得分:-1)

单班需要状态时。单身人士保持全球状态,静态阶级则不然。

例如,在注册表类周围做一个帮助:如果你有可更改的配置单元(HKey当前用户与HKEY本地计算机),你可以去:

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

现在对该单例的任何进一步调用都将在Local Machine配置单元上运行。否则,使用静态类,您必须指定每台Local Machine配置单元,或者使用ReadSubkeyFromLocalMachine之类的方法。