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


85

在决定使用单例还是静态类时,请列出设计注意事项。在执行此操作时,您被迫将两者进行对比,因此可以提出的任何对比也都有助于显示您的思维过程!此外,每个面试官都喜欢看示例性例子。:)


Java类不能使其静态,除非其内部类
Harshana 2015年

1
如果对象的状态确实很重要,则使用单例,否则,使用静态类。
Levent Divilioglu 2015年

Answers:


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

2
还要看一下此链接:codeofdoom.com/wordpress/2008/04/20/…–
Amit

4
“可以延迟加载信箱”-在C#中,仅在第一次引用静态成员时才调用静态构造函数。在PHP中,您可以使用自动加载功能使其仅在首次使用该类时运行。
mpen 2013年

Java静态初始化也很懒。单身人士那里没有积分。
MikeFHay 2013年

13

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

  • 可能会引入全球状态
  • 与其他多个班级紧密联系
  • 隐藏依赖项
  • 会使孤立地进行单元测试的课程变得困难

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

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


1
小巧的东西:单例类与依赖项注入技术兼容(只需将它们注入到其依赖类中,而无需对任何事物进行硬编码)-这允许隔离进行模拟和测试。它们也是用于管理已知在平台范围内受限的资源的有效设计模式。(经典示例是没有竞争管理的打印机。)
cdleary 2010年

@cdleary-同意;但是经典的实现通常在整个代码库中都保留Singleton.getInstance()。可以使用支持生命周期管理的POCO / POJO和IoC容器框架来实现用于管理资源,缓存等的“单例”(将类型注册为容器生存期,并且每个解析都将获得相同的实例)。
TrueWill 2010年

9
如果您想给自己的辛格尔顿一家适当的葬礼,请访问singletonfuneralservice.com
TrueWill 2010年

1
Hide Dependencies关于这一点的小补充:您可以通过使用Ninject例如实现控制反转模式。这允许您通过将类型的一个实例绑定SingletonScope到来仅使用它的一个实例,这意味着它将总是注入相同的实例,但该类不会是单例的。
LuckyLikey

8

我认为唯一的区别是语法:MySingleton.Current.Whatever()与MySingleton.Whatever()。正如大卫提到的那样,无论哪种情况,国家最终都是“静态的”。


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


7

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

总结Singleton的灵活性优势:

  • 单例可以轻松地转换为工厂
  • 可以轻松修改Singleton以返回不同的子类
  • 这样可以使应用程序更易于维护

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

可以对静态类进行类似的操作,但是在间接分派中会存在每次调用的开销。

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

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


4

将单例视为服务。它是提供一组特定功能的对象。例如

ObjectFactory.getInstance().makeObject();

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

相比之下,充满静态方法的类是您可能想要执行的操作的集合,并以相关组(The类)的形式组织。例如

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

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


4

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


13
单例也在运行时实例化...
递归

1
@recursive:可以控制单例实例化。
Nikit Batale,2010年

3
也许是一个更好的词是初始化(而不是实例化)
马龙

3

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

MyStaticClass.GetInstance().DoSomething();

基本上与

MyStaticClass.DoSomething();

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

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

服务不应意识到该对象是单例对象,应将该对象视为一个对象。

当然,可以将对象实现为实现细节和整体配置的一个方面,如果使事情变得更简单,则可以实现为单例。但是使用该对象的事物不必知道该对象是否为单例。


2

如果用“静态类”表示仅具有静态变量的类,则它们实际上可以维护状态。我的理解是,唯一的区别就是您访问该内容的方式。例如:

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

MySingleton.doSomething();

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


2
单例本身就是对象->它们可以作为参数传递。
Christian Klauser

2

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


1

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

几乎任何一个单例示例都说明了如何不这样做。


4
管理硬件资源的管理器类呢?日志文件类呢?
RJFalconer 2009年

0

如果单身人士是您可以处理的东西,可以对其进行清理,如果它是有限的资源(即仅其中之一),您不需要一直使用它并拥有某种内存或分配时的资源成本。

与具有静态字段的静态类相反,当您具有单例时,清理代码看起来更自然。

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


0

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

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


0

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


0

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

rp


0

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

一旦获得对单例对象的引用,就可以像其他任何对象一样使用它。如果对单例的引用存储在更早的位置,则客户端代码甚至可能不需要使用单例来了解其代码:

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

当您选择抛弃Singleton模式以使用IoC之类的真实模式时,这显然使事情变得容易。



0

我认为,当您必须构造昂贵的资源池(例如数据库连接)时,Singleton比静态类更有意义。如果没有人使用过池,那么您对创建池就不会有兴趣(静态类将意味着您在加载类时会进行昂贵的工作)。


0

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


0

如前所述,Singleton就像一项服务。专业是它的灵活性。静态的,那么,您需要一些静态零件才能实现Singleton。

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

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

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


0

参考这个

概要:

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

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


-1

当单个类需要状态时。单例保持全局状态,静态类不保持全局状态。

例如,为注册表类提供帮助:如果您拥有可更改的配置单元(HKey当前用户与HKEY本地计算机),则可以执行以下操作:

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

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


1
我反对以这种方式使用Singleton ...保持状态徘徊是非常危险的,因为下一个调用者可能会忘记设置配置单元,并用错误的配置单元写入注册表... :-(这是可能是一个使人认为Singletons不好的滥用例子
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.