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

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

When should you use the singleton pattern instead of a static class?

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


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

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

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

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

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


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

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


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

总结Singleton的灵活性优势:

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


带有大量静态变量的静态类有点麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * 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;    

    }
   }

具有实际实例的单例更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * 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;    

    }
   }

该状态仅在实例中处于。

因此可以稍后修改单例以进行池化,线程本地实例等。
而且,无需修改任何已编写的代码即可获得好处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;
       }

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

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

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


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

1
ObjectFactory.getInstance().makeObject();

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

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

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

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


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


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

1
MyStaticClass.GetInstance().DoSomething();

基本上与

1
MyStaticClass.DoSomething();

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

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

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

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


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


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

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

1
MySingleton.doSomething();

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


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

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


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


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


参考这个

摘要:

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

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


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

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

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

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


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

rp


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


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

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


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

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

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


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

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

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

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


当您需要在运行时计算某些内容时,请使用单例模式,如果可以的话,这些内容应在编译时进行计算,例如查找表。


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

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

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

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


推荐阅读

    linux编译时显示命令?

    linux编译时显示命令?,系统,基础,工具,代码,百度,下来,网上,命令,内核,文件,L

    linux命令行执行py?

    linux命令行执行py?,系统,环境,官网,一致,文件,程序,脚本,源文件,后台,终端,l

    linux查看执行命令?

    linux查看执行命令?,系统,服务,情况,信息,命令,暂停,标准,概念,实时,第一,lin

    linux命令连续执行?

    linux命令连续执行?,连续,通信,工具,数据,代码,命令,设备,系统,发行,情况,如

    linux执行命令卡住?

    linux执行命令卡住?,系统,环境,密码,数据,信息,分析,软件,异常,服务,命令,Lin

    linux命令执行不动了?

    linux命令执行不动了?,系统,电脑,数据,管理,信息,密码,命令,环境,地方,分析,l

    显示linux网卡命令行?

    显示linux网卡命令行?,系统,信息,工具,网络,服务,电脑,网卡,技术指标,地址,

    linux脚步中执行命令?

    linux脚步中执行命令?,工具,代码,命令,名称,系统,连续,环境,发行,文件,终端,l

    linux命令显示内容?

    linux命令显示内容?,标准,系统,数据,命令,百度,实时,时间,信息,文件,内容,lin

    linux命令操作vim?

    linux命令操作vim?,系统,地址,工作,命令,模式,标准,信息,入口,连续,时间,Linu

    linux执行权限命令行?

    linux执行权限命令行?,地址,电脑,系统,数字,工作,权限,目录,文件,新增,信息,L

    linux常用显示命令?

    linux常用显示命令?,工作,地址,系统,信息,管理,命令,目录,标准,功能,常用命

    linux命令的执行时间?

    linux命令的执行时间?,时间,系统,周期,信息,命令,设备,环境,地址,基础,进程,l

    程序执行linux命令?

    程序执行linux命令?,系统,工作,地址,环境,信息,管理,命令,文件,目录,程序,lin

    linux运维操作命令?

    linux运维操作命令?,系统,网络,基础,标准,工具,信息,工作,命令,软件,数据,lin

    linux分页显示命令?

    linux分页显示命令?,工具,通信,命令,数据,信息,管道,标准,位置,一致,系统,lin

    linux命令行常见操作?

    linux命令行常见操作?,系统,工作,地址,管理,信息,命令,目录,一致,基础,标准,l

    linux执行2个命令?

    linux执行2个命令?,工作,系统,基础,命令,基础知识,信息,管理,在线,概念,第一

    linux中ps命令显示?

    linux中ps命令显示?,系统,信息,状态,进程,命令,多地,软件,工作,基础,报告,lin

    linux命令逐页显示?

    linux命令逐页显示?,系统,工作,地址,命令,网上,信息,百度,基础,标准,内容,在l