代码改变世界

创建类模式(五):单例(Singleton)

2016-10-27 13:47  阿诚de窝  阅读(345)  评论(0编辑  收藏  举报

定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式一般情况下通过使用private的构造函数确保了在一个应用中只产生一个实例,并且是自行实例化。

和静态变量的区别

虽然都是在任意地方可以访问到,但是静态变量或全局变量不能限制一个应用中只存在指定类的一个实例,而单例可以。

线程安全

如果是多线程应用,需要在创建时进行加锁操作,否则可能会产生多个实例。

UML

优点

  1. 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  2. 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。
  3. 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
  4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

缺点

  1. 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。当然,在特殊情况下,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。
  2. 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
  3. 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。

应用场景

  1. 要求生成唯一序列号的环境;
  2. 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
  3. 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
  4. 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。

示例

实现一个单例并调用其一个方法。

C++

线程不安全模式:

Main.cpp

 1 #include "stdafx.h"
 2 #include "stdlib.h"
 3 #include "Mgr.h"
 4 
 5 int _tmain(int argc, _TCHAR* argv[])
 6 {
 7     Mgr::GetInstance()->doSomething();
 8 
 9     system("pause");
10     return 0;
11 }
View Code

Mgr.h

 1 #pragma once
 2 //单例, 线程不安全.
 3 class Mgr
 4 {
 5 public:
 6     //获取唯一实例.
 7     static Mgr* GetInstance();
 8     //方法.
 9     void doSomething();
10 private:
11     static Mgr* _instance;
12     //私有构造函数, 保证不能在外部被实例化.
13     Mgr();
14 }
View Code

Mgr.cpp

 1 #include "stdafx.h"
 2 #include <iostream>
 3 #include "Mgr.h"
 4 
 5 using namespace std;
 6 
 7 Mgr* Mgr::_instance = 0;
 8 
 9 Mgr* Mgr::GetInstance()
10 {
11     if(_instance == 0)
12     {
13         _instance = new Mgr();
14     }
15     return _instance;
16 }
17 
18 Mgr::Mgr()
19 {
20 }
21 
22 void Mgr::doSomething()
23 {
24     cout << "单例 Mgr 干了某事。" << endl;
25 }
View Code

线程安全模式:

C++的多线程需要借助一些外部库实现,所以线程安全的单例也要根据不同的类库来看其不同的实现:

Boost(这篇文章同时也包括了类库无关的通用线程安全模式):http://www.cnblogs.com/ccdev/archive/2012/12/19/2825355.html

pThread:http://blog.csdn.net/joanlynnlove/article/details/7462254

C#

 1 using System;
 2 
 3 namespace DesignPattern
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Mgr.GetInstance().DoSomething();
10 
11             Console.Read();
12         }
13     }
14 
15     /// <summary>
16     /// 单例, 线程安全, 不能继承所以作为密封类存在.
17     /// </summary>
18     public sealed class Mgr
19     {
20         private static Mgr _instance;
21         private static readonly object lockObj = new object();
22 
23         /// <summary>
24         /// 获取唯一实例.
25         /// </summary>
26         /// <returns>唯一实例.</returns>
27         public static Mgr GetInstance()
28         {
29             if(_instance == null)
30             {
31                 lock(lockObj)
32                 {
33                     if(_instance == null)
34                     {
35                         _instance = new Mgr();
36                     }
37                 }
38             }
39             return _instance;
40         }
41 
42         /// <summary>
43         /// 私有构造函数, 保证不能在外部被实例化.
44         /// </summary>
45         private Mgr()
46         {
47         }
48 
49         /// <summary>
50         /// 方法.
51         /// </summary>
52         public void DoSomething()
53         {
54             Console.WriteLine("单例 Mgr 干了某事。");
55         }
56     }
57 }
View Code

Java

Main.java

1 public class Main
2 {
3     public static void main(String[] args)
4     {
5         Mgr.getInstance().doSomething();
6     }
7 }
View Code

Mgr.java

 1 /**
 2  * 单例, 线程安全, 不能继承所以作为最终类存在.
 3  */
 4 public final class Mgr
 5 {
 6     private static Mgr _instance;
 7     
 8     /**
 9      * 获取唯一实例.
10      * @return 唯一实例.
11      */
12     public static Mgr getInstance()
13     {
14         if(_instance == null)
15         {
16             synchronized(Mgr.class)
17             {
18                 if(_instance == null)
19                 {
20                     _instance = new Mgr();
21                 }
22             }
23         }
24         return _instance;
25     }
26     
27     /**
28      * 私有构造函数, 保证不能在外部被实例化.
29      */
30     private Mgr()
31     {
32     }
33     
34     /**
35      * 方法.
36      */
37     public void doSomething()
38     {
39         System.out.println("单例 Mgr 干了某事。");
40     }
41 }
View Code

AS3

Main.as

 1 package
 2 {
 3     import flash.display.Sprite;
 4     
 5     public class Main extends Sprite
 6     {
 7         public function Main()
 8         {
 9             Mgr.getInstance().doSomething();
10         }
11     }
12 }
View Code

Mgr.as

 1 package
 2 {
 3     import flash.concurrent.Mutex;
 4     
 5     /**
 6      * 单例, 线程安全, 不能继承所以作为最终类存在.
 7      */
 8     public final class Mgr
 9     {
10         private static var _instance:Mgr;
11         private static var _mutex:Mutex = new Mutex();
12         
13         /**
14          * 获取唯一实例.
15          * @return 唯一实例.
16          */
17         public static function getInstance():Mgr
18         {
19             if(_instance == null)
20             {
21                 _mutex.lock();
22                 try
23                 {
24                     if(_instance == null)
25                     {
26                         _instance = new Mgr(new SingletonEnforcer());
27                     }
28                 }
29                 finally
30                 {
31                     _mutex.unlock();
32                 }
33             }
34             return _instance;
35         }
36         
37         /**
38          * AS3 中没有私有构造函数, 所以使用包外类来保证其不能在外部被实例化.
39          * @param singletonEnforcer 单例类实现对象.
40          */
41         public function Mgr(singletonEnforcer:SingletonEnforcer)
42         {
43             if(singletonEnforcer == null)
44             {
45                 throw new Error("单例类不能进行实例化!");
46             }
47         }
48         
49         /**
50          * 方法.
51          */
52         public function doSomething():void
53         {
54             trace("单例 Mgr 干了某事。");
55         }
56     }
57 }
58 
59 class SingletonEnforcer{}
View Code

我的经验总结

单例模式是很常见的模式,同时其存在比较多的变种,下面说说我遇到过的比较优秀的单例模式的变种模式。

扩展:多例模式

 

单例模式在Flex框架中的变种

Adobe的Flex框架中的Manager使用的模式是在单例的基础上进行的扩展的一种模式,其目的是为了方便替换实现类,大家可以自己下载Flex SDK来查看源码,或者来看我仿写的一个简单的例子:

1.管理类的接口

首先我们需要一个接口来规定这个管理类需要实现的方法:

1 package
2 {
3     public interface IMgr
4     {
5         function doSomething():void;
6     }
7 }
View Code

2.管理类的实现类

实现管理类接口的实现类,我们可以根据需求自己添加多个新的管理实现类:

实现一:

 1 package
 2 {
 3     public class MgrImpl implements IMgr
 4     {
 5         public function doSomething():void
 6         {
 7             trace("调用了管理类的实现类的方法。");
 8         }
 9     }
10 }
View Code

实现二:

 1 package
 2 {
 3     public class MyMgrImpl implements IMgr
 4     {
 5         public function doSomething():void
 6         {
 7             trace("调用了我自定义的管理类的实现类的方法。");
 8         }
 9     }
10 }
View Code

3.注入类

我们需要一个类来指定我们的管理类实际上使用的实现类是谁:

 1 package
 2 {
 3     import flash.utils.Dictionary;
 4     
 5     public class Injector
 6     {
 7         private static var _classMap:Dictionary = new Dictionary();
 8         
 9         public static function setClass(key:Class, value:Class)
10         {
11             _classMap[key] = value;
12         }
13         
14         public static function getClass(key:Class):Object
15         {
16             return new _classMap[key]();
17         }
18     }
19 }
View Code

4.管理类

这个管理类是一个静态类,我们可以看做一个壳子,实际上是专门给外部调用的:

 1 package
 2 {
 3     public final class Mgr
 4     {
 5         private static var _impl:IMgr;
 6         
 7         private static function get impl():IMgr
 8         {
 9             if(_impl == null)
10             {
11                 _impl = IMgr(Injector.getClass(Mgr));
12             }
13             return _impl;
14         }
15         
16         public static function doSomething():void
17         {
18             impl.doSomething();
19         }
20     }
21 }
View Code

测试

使用第一个实现类:

 1 package
 2 {
 3     import flash.display.Sprite;
 4     
 5     public class Main extends Sprite
 6     {
 7         public function Main()
 8         {
 9             Injector.setClass(Mgr, MgrImpl);
10             Mgr.doSomething();//调用了管理类的实现类的方法。
11         }
12     }
13 }
View Code

使用第二个实现类:

 1 package
 2 {
 3     import flash.display.Sprite;
 4     
 5     public class Main extends Sprite
 6     {
 7         public function Main()
 8         {
 9             Injector.setClass(Mgr, MyMgrImpl);
10             Mgr.doSomething();//调用了我自定义的管理类的实现类的方法。
11         }
12     }
13 }
View Code