我是如何学习设计模式的九:轻松一下,学习一个简单的:单例模式(最简单,但是也是最有用的)
我故意把单例模式放在中间 讲解 ,因为其原理简单,但是作用是最大的,让大家轻松点,轻松点的目的是为下一个超级重要的模式做准备:工厂模式,本人将把世界有关工厂模式的一切易学的资料吃透,整理给大家阅读,学习!
单例模式
理论
1
C#单例模式的三种写法
原文:http://blog.sina.com.cn/s/blog_4b8589b70100c5mt.html
第一种最简单,但没有考虑线程安全,在多线程时可能会出问题,不过俺从没看过出错的现象,表鄙视我……
public class Singleton
{
private static Singleton _instance = null;
private Singleton(){}
public static Singleton CreateInstance()
{
if(_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
第二种考虑了线程安全,不过有点烦,但绝对是正规写法,经典的一叉
public class Singleton
{
private volatile static Singleton _instance = null;
private static readonly object lockHelper = new object();
private Singleton(){}
public static Singleton CreateInstance()
{
if(_instance == null)
{
lock(lockHelper)
{
if(_instance == null)
_instance = new Singleton();
}
}
return _instance;
}
}
第三种可能是C#这样的高级语言特有的,实在懒得出奇
public class Singleton
{
private Singleton(){}
public static readonly Singleton instance = new Singleton();
}
哦,shit!
2
创建一个单体模式类,必须符合三个条件:
1:私有构造函数(防止其他对象创建实例);
2:一个单体类型的私有变量;
3:静态全局获取接口
下面我写一个类,为了看是不是单体,就加了一个计数器,如果是同一个类,那么这个类的计数每次调用以后就应该自动加一,而不是重新建对象归零:
.NET c# 单体模式
using System;
using System.Threading;
public class Singleton{
private int ObjCount=0;
private Singleton(){
Console.WriteLine("创建对象");
}
private static Singleton objInstance = null;
public static Singleton getInstance() {
if (objInstance==null) objInstance=new Singleton();
return objInstance;
}
public void ShowCount(){
ObjCount++;
Console.WriteLine("单个对象被调用了{0}次",ObjCount);
}
};
然后我们来测试一下:
public class ConsoleTest{
public static void Main(string[] args){
Console.WriteLine("开始执行单体模式");
for(int i=0;i<5;i++){
Singleton.getInstance().ShowCount();
}
Console.ReadLine();
}
};
我在这个Main里面执行了5次,看看输出的结果:
开始执行单体模式
创建对象
单个对象被调用了1次
单个对象被调用了2次
单个对象被调用了3次
单个对象被调用了4次
单个对象被调用了5次
在这里可以看出,每次都是使用的同一个对象,实现了单体。
为了测试在多线程下面是否是单体,下面我写了一个多线程测试的:
class ApartmentTest
{
public static void RunMoreThread()
{
Thread newThread = new Thread(new ThreadStart(ThreadSingleMethod));
newThread.SetApartmentState(ApartmentState.MTA);
Console.WriteLine("ThreadState: {0}, ApartmentState: {1},ManagedThreadId:{2}", newThread.ThreadState, newThread.GetApartmentState(),newThread.ManagedThreadId );
newThread.Start();
}
public static void ThreadSingleMethod() {
Singleton.getInstance().ShowCount();
}
};
然后每次for循环执行ApartmentTest.RunMoreThread();
再看看输出的结果:开始执行单体模式
ThreadState: Unstarted, ApartmentState: MTA,ManagedThreadId:3
创建对象
单个对象被调用了1次
ThreadState: Unstarted, ApartmentState: MTA,ManagedThreadId:4
单个对象被调用了2次
ThreadState: Unstarted, ApartmentState: MTA,ManagedThreadId:5
单个对象被调用了3次
ThreadState: Unstarted, ApartmentState: MTA,ManagedThreadId:6
单个对象被调用了4次
ThreadState: Unstarted, ApartmentState: MTA,ManagedThreadId:7
单个对象被调用了5次
根据ManagedThreadId,可以看出不同的线路访问达到了单体,OK!
c#的单体模式介绍完毕;
本文来自: 脚本之家(www.jb51.net) 详细出处参考:http://www.jb51.net/article/13117.htm
3
单例模式有以下的特点:
1 :单例类只可有一个实例。
2 :单例类必须自己创建自己这惟一的实例。
3 :单例类必须给所有其他对象提供这一实例。
一般创建单例我们常用的有两种方式:
1:静态变量法,也叫惰性实例化
2:双重锁定
静态变量法非常好理解,.net的运行机制决定了静态变量可以形成单件,静态变量是一个存储在内存中的变量.它的机制可以保证同一时间只会存在一个变量.为此我们非常容易的可以写出这样的程序:
1:
Code
非线程安全的单件#region 非线程安全的单件
/**//// <SUMMARY>
/// 定义自身的静态变量实例,默认值为null
/// </SUMMARY>
private static Singleton instance = null;
/**//// <SUMMARY>
/// 私有构造函数
/// 之所有不是public类型的,因为是为了避免发生new Singleton()产生更多的实例
/// </SUMMARY>
private Singleton()
{ }
/**//// <SUMMARY>
/// 生成实例方法
/// </SUMMARY>
/// <RETURNS></RETURNS>
public static Singleton getInstance()
{
//如果实例没有被初始化则实例化变量
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
#endregion
这种对于单线程运行的程序来说是绝对没有问题的,例如运行一个asp.net程序,一般网站都基本用不到多线程,所有上面的例子就足够满足.但是如果是多线程呢?如果同时有两个线开始判断instance==null,如果此时都为真,则创建了两上实例,这样就违背了单件的原则.为此可进行下修改:
2:
Code
线程安全的单件(静态变量方法)#region 线程安全的单件(静态变量方法)
/**//// <SUMMARY>
/// 定义自身的静态变量实例,在程序运行时初始化变量
/// instance被申请成readonly,也是为了避免给实例重新赋值.
/// </SUMMARY>
private static readonly Singleton instance = new Singleton();
/**//// <SUMMARY>
/// 私有构造函数
/// 之所有不是public类型的,因为是为了避免发生new Singleton()产生更多的实例
/// </SUMMARY>
private Singleton()
{ }
/**//// <SUMMARY>
/// 生成实例方法
/// </SUMMARY>
/// <RETURNS></RETURNS>
public static Singleton getInstance()
{
return instance;
}
#endregion
这种方法在程序初始化时对类实例进行了初始化,利用静态变量以及私有构造方法完成了单件的生成.之所以称这种方式是惰性实例化,是因为,无论你是否调用这个实例,它都会被创建.
双重锁定:
我们可以对方法1进行下改造,它的缺点就是非线程安全,既然有缺点当然就要改正了.
3:
Code
线程安全的单件(双重锁定方法1:线程安全但性能较差)#region 线程安全的单件(双重锁定方法1:线程安全但性能较差)
/**//// <SUMMARY>
/// 定义自身的静态变量实例,默认值为null
/// </SUMMARY>
private static Singleton instance = null;
/**//// <SUMMARY>
/// 创建一个object对象,同样它也是静态只读,用来实现锁定功能
/// </SUMMARY>
private static readonly object olock = new object();
/**//// <SUMMARY>
/// 私有构造函数
/// 之所有不是public类型的,因为是为了避免发生new Singleton()产生更多的实例
/// </SUMMARY>
private Singleton()
{ }
/**//// <SUMMARY>
/// 生成实例方法
/// </SUMMARY>
/// <RETURNS></RETURNS>
public static Singleton getInstance()
{
//取得实例的时候先锁定对象,然后判定是否存在
lock (olock)
{
//如果实例没有被初始化则实例化变量
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
#endregion
这个方法是线程式安全的单件模式,但是它每次生成单件的时候都要给对象加锁,这样也是一种性能消耗.上面说了有缺点就要改,这个方法当然也不例外:
4:
Code
线程安全的单件(双重锁定方法2:线程安全而且性能优越)#region 线程安全的单件(双重锁定方法2:线程安全而且性能优越)
/**//// <SUMMARY>
/// 定义自身的静态变量实例,默认值为null
/// instance被申请成readonly,也是为了避免给实例重新赋值.
/// </SUMMARY>
private static Singleton instance = null;
/**//// <SUMMARY>
/// 创建一个object对象,同样它也是静态只读,用来实现锁定功能
/// </SUMMARY>
private static readonly object olock = new object();
/**//// <SUMMARY>
/// 私有构造函数
/// 之所有不是public类型的,因为是为了避免发生new Singleton()产生更多的实例
/// </SUMMARY>
private Singleton()
{ }
/**//// <SUMMARY>
/// 生成实例方法
/// </SUMMARY>
/// <RETURNS></RETURNS>
public static Singleton getInstance()
{
if (instance == null)
{
//取得实例的时候先锁定对象,然后判定是否存在
lock (olock)
{
//如果实例没有被初始化则实例化变量
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
#endregion
这个方法在生成的实例的时候,先判断是否为空,如果不为空则不对对象进行加锁操作直接返回对象实例.同时这种双重锁定对比静态方法来看,有一个优势就是它的实例化延迟到类中,只有调用此类的实例时它才会生成实例.
上面都是常规的单件模式,其实并不是所有的情况都如上面一样.在抽象工厂的应用中经常结合单件模式来应用,使得工厂实例是唯一的.在上一篇用抽象工厂模式武装新闻组件 中,我用上了抽象工厂,当时在抽象工厂类实例的生成上并没有结合单件模式,所在在这想改造一下.我的程序思路是这样的,有一个抽象工厂基类AbstractFactoryClass,它包含一个public static AbstractFactoryClass GetInstance()方法,作用是生成它的派生类的实例,而这个生成过程是通过反射来完成的.这两个派生类分别是:AbstractFactory_China,AbstractFactory_US.这种情况下要想直接返回AbstractFactoryClass的实例是不可能的,因为它是抽象类,不能直接new AbstractFactoryClass().
先看下这三个类的类图:
代码如下:
Code
//把抽象类设置成静态变量,避免多次加载程序集
private static AbstractFactoryClass instance=null ;
/**//// <SUMMARY>
/// 创建一个object对象,同样它也是静态只读,用来实现锁定功能
/// </SUMMARY>
private static readonly object olock = new object();
public static AbstractFactoryClass GetInstance()
{
//应用双重锁定来产生工厂单件对象
if (instance == null)
{
lock (olock)
{
if (instance == null)
{
//取得当前工厂名称
string factoryName = ConfigurationSettings.AppSettings["factoryName"].ToString();
if (factoryName != "")
instance = (AbstractFactoryClass)Assembly.Load("AbstractFactoryCompent").CreateInstance(factoryName);
else
instance = null;
}
}
}
return instance;
}
上面的代码因为基类是抽象类所有没有上面常规程序中的私有构造函数,这种生成抽象工厂类派生类实例的案例并不适用于静态方法构造单件的情况,因为你不能直接通过new的方式来初始化类实例.
应用场景
在.NET框架的重要组成部分Remoting中,远程对象(Remote Object)有两种激活方式:服务器端激活方式和客户端激活方式。采用服务器端激活方式的对象又分为两种类型:Singleton对象和SingleCall对象。Singleton 对象是这样的对象:无论该对象有多少个客户端调用,它总是只有一个实例,由这个实例来处理所有的客户端请求。相反地,若将远程对象声明为 SingleCall,则系统会为每次客户端方法的调用创建一个新对象,即使这些方法调用来自同一个客户端,也即,对象只在方法调用持续期间存在,一旦方法调用结束,该对象就会被销毁。显而易见,这里的Singleton对象就是设计模式Singleton思想在.NET中的应用
我们现在要做一个网络游戏的服务端程序,需要考虑怎么样才能承载大量的用户。在做WEB程序的时候有各种负载均衡的方案,不管是通过硬件实现还是软件实现,基本的思想就是有一个统一的入口,然后由它来分配用户到各个服务器上去。
泛型写的单例例子
Singleton
using System;
using System.Collections.Generic;
using System.Text;
namespace Pattern.Singleton
{
/**//// <summary>
/// 泛型实现单例模式
/// </summary>
/// <typeparam name="T">需要实现单例的类</typeparam>
public class Singleton<T> where T : new()
{
/**//// <summary>
/// 返回类的实例
/// </summary>
public static T Instance
{
get { return SingletonCreator.instance; }
}
class SingletonCreator
{
internal static readonly T instance = new T();
}