Singleton模式由于其实现相对简单所以号称设计模式中最简单的模式
但是static通常会给你造成一些障碍不信啊那你就看看吧而且还有几个Effective C#条款 :) 希望不会让你失望
该篇并没有涉及到Net Framework源码就算是挂羊头卖狗肉吧希望延续上篇的高质量
让我们先来写个简单的SqlHelper吧封装SQL数据库的操作
using System;
using SystemData;
using SystemDataSqlClient;
namespace APeng
{
public class SqlHelper
{
private string m_connString = Data Source=(local); +
User ID=sa;Password=password;Initial Catalog=discuz;Pooling=true;
//Sql数据库连接字符串
public SqlDataReader ExecuteReader(CommandType cmdType string cmdText params SqlParameter[] cmdParms) {
SqlCommand cmd = new SqlCommand();
SqlConnection conn = new SqlConnection(m_connString);
try
{
PrepareCommand(cmd conn cmdType cmdText cmdParms);
SqlDataReader rdr = cmdExecuteReader(CommandBehaviorCloseConnection);
cmdParametersClear();
return rdr;
}
catch (Exception ex)
{
connClose();
connDispose();
throw ex;
}
}
private void PrepareCommand(SqlCommand cmd SqlConnection conn CommandType cmdType string cmdText SqlParameter[] cmdParms)
{
if (connState != ConnectionStateOpen)
connOpen();
cmdConnection = conn;
cmdCommandText = cmdText;
cmdCommandType = cmdType;
if (cmdParms != null)
{
foreach (SqlParameter parm in cmdParms)
cmdParametersAdd(parm);
}
}
}
}
这段代码大家应该很熟悉了接下来让我们来使用它
using System;
using SystemData;
using SystemDataSqlClient;
namespace APeng
{
class Program
{
static void Main(string[] args)
{
SqlHelper helper = new SqlHelper();
string cmdText = select fidname from dnt_forums;
using (SqlDataReader reader = helperExecuteReader(CommandTypeText cmdText null))
{
while (readerRead())
{
ConsoleWriteLine(编号: + reader[fid]);
ConsoleWriteLine(名称: + reader[name]);
}
}
ConsoleRead();
}
}
}
程序正常输出: 编号: 名称:版块 编号: 名称:版块
很简单不是嘛
接下来我们将要优化这个SqlHelper
一) 将SqlHelper中的private string m_connString = 修改成 private static readonly string m_connString =
这个修改是否有必要呢? (如果没能给我带来什么好处我为什么要修改呢所以你得说服我!)
小菜先在A地方实例化一个SqlHelper
SqlHelper helper = new SqlHelper();
那么会有如下构造过程:
为数据成员m_connString分配内存空间此时空间存储数据为null
(如果是值类型如intfloatdouble等空间存储数据为如果是引用类型空间存储数据为null下面还会详细说明)
执行数据成员m_connString的初始化语句也就是上面的private string m_connString =
(那么现在空间存储数据为)
执行SqlHelper的构造函数
小菜然后在B地方又实例化一个SqlHelper
SqlHelper helper = new SqlHelper();
那么会有如下构造过程:
为数据成员m_connString分配内存空间此时空间存储数据为null
执行数据成员m_connString的初始化语句也就是上面的private string m_connString =
(那么现在空间存储数据为)
执行SqlHelper的构造函数
噢有没有搞错啊怎么一直为m_connString分配内存空间
而且老是把m_connString空间存储数据置为相同的
该死的你就不能聪明点做一次就够了(浪费我们宝贵的时间和宝贵的内存资源)
唉看来我们得自己动手优化了怎么优化呢?
等等小菜刚才说什么来着?修改成private static readonly string m_connString =
那它能改变这种状况吗?
小菜先在A地方实例化一个SqlHelper
SqlHelper helper = new SqlHelper();
那么会有如下构造过程:
为静态数据成员m_connString分配内存空间此时空间存储数据为null
(如果是值类型如intfloatdouble等空间存储数据为如果是引用类型空间存储数据为null下面还会详细说明)
执行静态数据成员m_connString的初始化语句也就是上面的private static readonly string m_connString =
(那么现在空间存储数据为)
执行SqlHelper的构造函数
小菜然后在B地方又实例化一个SqlHelper
SqlHelper helper = new SqlHelper();
那么会有如下构造过程:
执行SqlHelper的构造函数
看来真不错变聪明了只分配了一次m_connString的内存空间只初始化了一次m_connString
看来多亏了static
注意:这里应该引起你的关注
有一些朋友的代码中时常出现为值类型成员赋为引用类型赋null
public class Person//人类
{
private int _age = ;//年龄
}
或者
public class Person//人类
{
private Address _address = null;//地址对象
}
这其实是无必要的和上面new SqlHelper()的构造过程一样在分配数据成员的内存空间时便会为值类型成员赋为引用类型赋null.如果我们显示的赋值的话不但没有任何帮助反而会增加指令的操作影响效率.
Effective C# 中有介绍
其实上面主要的知识点是对象的构造过程让我们来复习一下吧
第一种:
1.当我们调用类里的静态方法时如果类里面的静态成员还未初始化那么这个类的所有静态成员依据在类里面出现的次序初始化.
2.为静态成员分配内存空间此时空间存储数据为或null
3.执行静态成员的初始化语句(也就是赋值语句)
4.执行类的静态构造函数
很明显这样的话如果我们第二次调用类里的静态方法时1234都不会被执行了.
第二种:
1.当我们对类实例化的时候如果类里面的静态成员还未初始化那么这个类的所有静态成员依据在类里面出现的次序初始化.
2.为静态成员分配内存空间此时空间存储数据为或null
3.执行静态成员的初始化语句(也就是赋值语句)
4.执行类的静态构造函数
5 为普通成员分配内存空间此时空间存储数据为或null
6.执行普通成员的初始化语句(也就是赋值语句)
执行类的构造函数
很明显这样的话如果我们第二次实例化类1234也都不会被执行只会执行567
二) 将public SqlDataReader ExecuteReader() 修改成 public static SqlDataReader ExecuteReader()
修改不修改关键看什么呢?
如果该方法无需保持或变动跟对象有关的状态则说明该方法与任何实例无关所以可设计成static方法
我们的ExecuteReader()满足上面条件无需操持对象有关状态而且无需变动跟对象有关的状态
三) 将public class SqlHelper 修改为 public static class SqlHelper
经过上面的修改后我们的SqlHelper已经是一个合适工具类它无需被实例化使用abstract无需被继承使用sealed
可是没有public abstract sealed class SqlHelper 但有static 二者是等效的称为静态类
Math类相信大家都用的很爽吧比如MathAbs()取绝对值等方法
很明显Math也是做为一个工具类所以在Net中也被设计成静态类
注意:有些朋友要说了那SqlHelper可不可以使用单件模式设计
可以可是不合适做为一个工具类它根本无需被实例化一次都不要
有些朋友要说了讲单件模式讲到哪里去了都不知道但小菜觉得区分不好static单件模式是用不好的滥用误用更是不在话下
接下来就正式来说单件模式吧! (只允许实例化一次)
)第一种单件模式
public sealed class Singleton
{
private static readonly Singleton _instance = new Singleton();
private Singleton()
{ }
public static Singleton Instance
{
get
{
return _instance;
}
}
public void DoSomething()
{
ConsoleWriteLine(做些事情);
}
}
接下来我们来用用测试一下
static void Main(string[] args)
{
Singleton s = SingletonInstance;
Singleton s = SingletonInstance;
sDoSomething();//做些事情
sDoSomething();//做些事情
ConsoleWriteLine(objectReferenceEquals(s s));//是否为相同实例true
}
恩不错大家觉得上面的设计怎么样?
其一: 如果很看注性能的话或者Singleton很浪费资源的话使用lazyinit会比较好一点当需要用时才初始化
但通常上面的代码是够用的
其二: 由于静态成员的初始化时间很难控制所以如果是静态引用类型的话放在静态构造函数中初始化会更加适合
这里就用到了前面讲到的对象构造顺序如果不清楚的话建议拉到前面在看一下很重要
这也是Effective C#中有介绍
所以代码修改为
public sealed class Singleton
{
private static readonly Singleton _instance;
private Singleton()
{ }
static Singleton()
{
_instance = Singleton();
}
public static Singleton Instance
{
get
{
return _instance;
}
}
public void DoSomething()
{
ConsoleWriteLine(做些事情);
}
}
如果对性能不是太讲究的话推荐该做法而且适合多线程
二)第二种单件模式lazyinit
public sealed class Singleton
{
private static Singleton _instance;
private Singleton()
{ }
public static Singleton Instance
{
get
{
if (_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
public void DoSomething()
{
ConsoleWriteLine(做些事情);
}
}
代码也很清晰但该单件模式只适用于单线程不适合与多线程
为什么呢?
线程取SingletonInstance执行到第行if(_instance == null) 成立进入停下
线程取SingletonInstance执行到第行if(_instance == null) 也同样成立
进入执行第行_instance = new Singleton() 设为obj 停下
线程继续执行同样执行第行_instance = new Singleton() 设为obj
obj与obj是不同已经不是单件是双件了线程越多可能多件都有可能
这样的话很多朋友马上会相到把 if(_instance==null){/*省略*/}锁上不就ok了
那就进入第三种单件模式
三)第三种单件模式
public sealed class Singleton
{
private static Singleton _instance;
private static object _lockHelper = new object();
private Singleton()
{ }
public static Singleton Instance
{
get
{
lock (_lockHelper)
{
if (_instance == null)
{
_instance = new Singleton();
}
}
return _instance;
}
}
public void DoSomething()
{
ConsoleWriteLine(做些事情);
}
}
看来该单件模式支持多线程但看来并不是太聪明
如果_instance已经被初始化然后每次线程进入还是需要同步很明显效率下降
我们只需要第一次初始化的时候同步之后不要同步是最好的效率也高
看来doublecheck即双检查会大大提高效率
那就进入第四种单件模式
四)第四种单件模式
public sealed class Singleton
{
private static Singleton _instance;
private static object _lockHelper = new object();
private Singleton()
{ }
public static Singleton Instance
{
get
{
if (_instance == null)
{
lock (_lockHelper)
{
if (_instance == null)
{
_instance = new Singleton();
}
}
}
return _instance;
}
}
public void DoSomething()
{
ConsoleWriteLine(做些事情);
}
}
记得看到过有人这么问为什么要双检查呢为什么不改成如下代码?
public static Singleton Instance
{
get
{
if(_instance == null)
{
lock(_lockHelper)
{
_instance = new Singleton();
}
}
return _instance
}
}
那我们来分析一下
线程执行到第行停下来
线程执行到第行因为线程在lock里面被阻塞停下来
线程继续执行第行初始化一个_instance设为obj退出lock区
线程进入lock里面也初始化一个_instance设为obj退出lock区
现在又是双件了所以为什么要叫doublecheck双检查也是来源与此
这个单件模式被用的最多但它就真的那么完美无缺吗? 不
因为上面的代码被编译编译器由于考虑时间和空间的问题会对代码进行优化指令的顺序可能也会被改变
所以在多线程中可能还是会出状况虽然这种概率很低但要是有解决方法为什么不用呢?
那就来看第五种单件模式
五)第五种单件模式
public sealed class Singleton
{
private static volatile Singleton _instance;
private static object _lockHelper = new object();
private Singleton()
{ }
public static Singleton Instance
{
get
{
if (_instance == null)
{
lock (_lockHelper)
{
if (_instance == null)
{
_instance = new Singleton();
}
}
}
return _instance;
}
}
public void DoSomething()
{
ConsoleWriteLine(做些事情);
}
}
小菜并没有做多少事只是把private static Singleton _instance;修改为private static volatile Singleton _instance;
volatile关键字有这么大魔力? 对
编译器保证对此_instance的读写操作都不会被优化
接下来我们来讲个集万千宠爱与一生的单件模式
六)第六个单件模式
public sealed class Singleton
{
private Singleton()
{ }
public static Singleton Instance
{
get
{
return Nestedinstance;
}
}
public void DoSomething()
{
ConsoleWriteLine(做些事情);
}
//嵌套类
private class Nested
{
internal static readonly Singleton instance;
private Nested()
{ }
static Nested()
{
instance = new Singleton();
}
}
}
return Nestedinstance保证了原子性
第一次执行它时
为Netsted的静态数据成员Singleton instance分配内存空间存储空间的值为null
执行instance的静态初始语句由于没有所以跳过
执行静态构造函数执行 instance = new Singleton() 初始化instance
返回instance对象
很明显用到的知识还是前面的对象构造顺序可见有多重要
到这里单件模式的多种实现都介绍完了