拼吾爱程序人生.Net编程Visual Studio.NET .Net Framework框架源码学习(第二篇 单件模式)

1  /  1  页   1 跳转 查看:350

.Net Framework框架源码学习(第二篇 单件模式)

.Net Framework框架源码学习(第二篇 单件模式)

Singleton模式,由于其实现相对简单,所以号称设计模式中最简单的模式.
但是static通常会给你造成一些障碍.不信啊,那你就看看吧,而且还有几个Effective C#条款 :) 希望不会让你失望.

该篇并没有涉及到.Net Framework源码,就算是挂羊头卖狗肉吧.希望延续上篇的高质量.
让我们先来写个简单的SqlHelper吧,封装SQL数据库的操作.

Code


这段代码大家应该很熟悉了,接下来让我们来使用它.

Code


程序正常输出: 编号:1 名称:版块1 编号:2 名称:版块2
很简单,不是嘛.

原文出处:http://www.cnblogs.com/a-peng/archive/2008/07/27/1252667.html

 感谢原创者的辛勤劳动,希望对您有所帮助,转载请注明原出处。
 您可能对 [Visual Studio.NET] 的这些文章也感兴趣:

VS2008开发.NET 2.0环境下的 ASP.NET AJAX 网站
如何用.NET技术在线生成网站LOGO
.Net Micro Framework研究—窗体控件
.Net 2.0 新功能:重构(Refactoring)
.NET框架4.0中都有些什么?
升级到Visual Studio 2008的10个技巧
Using the Task Scheduler in Vista and Windows Server 2008
.NET:七道最经典的asp.net页面传值题
.NET4.0中Windows Workflow Foundation 4.0的功能预览
Phoenix编译器框架说明
 

回复:.Net Framework框架源码学习(第二篇 单件模式)

接下来我们将要优化这个SqlHelper
一) 将SqlHelper中的private string m_connString = "......" 修改成 private static readonly string m_connString = "......"

这个修改是否有必要呢? (如果没能给我带来什么好处,我为什么要修改呢,所以你得说服我!)

小菜先在A地方实例化一个SqlHelper.
SqlHelper helper1 = new SqlHelper();
那么会有如下构造过程:
1.为数据成员m_connString分配内存空间,此时空间存储数据为null
  (如果是值类型如int,float,double等,空间存储数据为0,如果是引用类型空间存储数据为null,下面还会详细说明)
2.执行数据成员m_connString的初始化语句,也就是上面的private string m_connString = "......"
  (那么现在空间存储数据为"......")
3.执行SqlHelper的构造函数

小菜然后在B地方又实例化一个SqlHelper.
SqlHelper helper2 = new SqlHelper();
那么会有如下构造过程:
1.为数据成员m_connString分配内存空间,此时空间存储数据为null
2.执行数据成员m_connString的初始化语句,也就是上面的private string m_connString = "......"
  (那么现在空间存储数据为"......")
3.执行SqlHelper的构造函数

噢,有没有搞错啊,怎么一直为m_connString分配内存空间.
而且老是把m_connString空间存储数据置为相同的 "......"
该死的,你就不能聪明点,做一次就够了.(浪费我们宝贵的时间和宝贵的内存资源)

唉,看来我们得自己动手优化了.怎么优化呢?
等等,小菜刚才说什么来着?修改成private static readonly string m_connString = "......".
那它能改变这种状况吗?

小菜先在A地方实例化一个SqlHelper.
SqlHelper helper1 = new SqlHelper();
那么会有如下构造过程:
1.为静态数据成员m_connString分配内存空间,此时空间存储数据为null
  (如果是值类型如int,float,double等,空间存储数据为0,如果是引用类型空间存储数据为null,下面还会详细说明)
2.执行静态数据成员m_connString的初始化语句,也就是上面的private static readonly string m_connString = "......"
(那么现在空间存储数据为"......")
3.执行SqlHelper的构造函数

小菜然后在B地方又实例化一个SqlHelper.
SqlHelper helper2 = new SqlHelper();
那么会有如下构造过程:
1.执行SqlHelper的构造函数

看来真不错,变聪明了,只分配了一次m_connString的内存空间,只初始化了一次m_connString.
看来多亏了static.

注意:这里应该引起你的关注.
有一些朋友的代码中时常出现为值类型成员赋0,为引用类型赋null   
public class Person//人类
{
    private int _age = 0;//年龄
}
或者
public class Person//人类
{
    private Address _address = null;//地址对象
}
这其实是无必要的,和上面new SqlHelper()的构造过程一样.在分配数据成员的内存空间时,便会为值类型成员赋0,为引用类型赋null.如果我们显示的赋值的话,不但没有任何帮助,反而会增加指令的操作,影响效率.
Effective C# 中有介绍

其实上面主要的知识点是对象的构造过程,让我们来复习一下吧.
第一种:
1.当我们调用类里的静态方法时,如果类里面的静态成员还未初始化,那么这个类的所有静态成员依据在类里面出现的次序初始化.
2.为静态成员分配内存空间,此时空间存储数据为0或null
3.执行静态成员的初始化语句(也就是赋值语句)
    这里的赋值代码会在编译的时候被移到静态构造函数中执行,可看本文的@Superstone的评论
4.执行类的静态构造函数

很明显,这样的话如果我们第二次调用类里的静态方法时,1,2,3,4都不会被执行了.

第二种:
1.当我们对类实例化的时候,如果类里面的静态成员还未初始化,那么这个类的所有静态成员依据在类里面出现的次序初始化.
2.为静态成员分配内存空间,此时空间存储数据为0或null
3.执行静态成员的初始化语句(也就是赋值语句)
    这里的赋值代码会在编译的时候被移到静态构造函数中执行,可看本文的@Superstone的评论
4.执行类的静态构造函数
5.  为普通成员分配内存空间,此时空间存储数据为0或null
6.执行普通成员的初始化语句(也就是赋值语句)
      这里的赋值代码会在编译的时候被移到构造函数中执行,可看本文的@Superstone的评论
7.  执行类的构造函数

很明显,这样的话如果我们第二次实例化类,1,2,3,4也都不会被执行,只会执行5,6,7
 

回复:.Net Framework框架源码学习(第二篇 单件模式)

二) 将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类相信大家都用的很爽吧.比如Math.Abs()取绝对值等方法.
很明显Math也是做为一个工具类,所以在.Net中也被设计成静态类.

注意:有些朋友要说了,那SqlHelper可不可以使用单件模式设计.
可以,可是不合适.做为一个工具类,它根本无需被实例化,一次都不要.
有些朋友要说了,讲单件模式讲到哪里去了都不知道,但小菜觉得区分不好static,单件模式是用不好的.滥用误用更是不在话下.

接下来就正式来说单件模式吧! (只允许实例化一次)
1)第一种单件模式

Code


接下来,我们来用用测试一下

Code


恩,不错.大家觉得上面的设计怎么样?
其一: 如果很看注性能的话,或者Singleton很浪费资源的话,使用lazy-init会比较好一点.当需要用时才初始化.
        但通常上面的代码是够用的.
其二: 由于静态成员的初始化时间很难控制,所以如果是静态引用类型的话,放在静态构造函数中初始化会更加适合.
        这里就用到了前面讲到的对象构造顺序.如果不清楚的话,建议拉到前面在看一下,很重要.
        这也是Effective C#中有介绍.
所以代码修改为

Code


如果对性能不是太讲究的话,推荐该做法.而且适合多线程.

二)第二种单件模式lazy-init

Code


代码也很清晰,但该单件模式只适用于单线程.不适合与多线程
为什么呢?
线程1取Singleton.Instance执行到第12行if(_instance == null) 成立进入停下
线程2取Singleton.Instance执行到第12行if(_instance == null) 也同样成立
      进入执行第14行_instance = new Singleton() 设为obj1 停下
线程1继续执行同样执行第14行_instance = new Singleton() 设为obj2
obj1与obj2是不同.已经不是单件,是双件了,线程越多,可能多件都有可能.

这样的话,很多朋友马上会相到把 if(_instance==null){/*省略*/}锁上.不就ok了.
那就进入第三种单件模式.
 

回复:.Net Framework框架源码学习(第二篇 单件模式)

三)第三种单件模式

Code


看来该单件模式支持多线程,但看来并不是太聪明.

如果_instance已经被初始化,然后每次线程进入还是需要同步,很明显效率下降.
我们只需要第一次初始化的时候同步,之后不要同步是最好的.效率也高.
看来double-check即双检查,会大大提高效率.
那就进入第四种单件模式.

四)第四种单件模式

Code


记得看到过有人这么问.为什么要双检查呢.为什么不改成如下代码?

Code


那我们来分析一下.
线程1执行到第8行停下来
线程2执行到第7行,因为线程1在lock里面,被阻塞,停下来
线程1继续执行第9行,初始化一个_instance设为obj1,退出lock区
线程2进入lock里面,也初始化一个_instance设为obj2,退出lock区
现在又是双件了.所以为什么要叫double-check双检查,也是来源与此.

这个单件模式被用的最多.但它就真的那么完美无缺吗? 不
因为上面的代码被编译,编译器由于考虑时间和空间的问题,会对代码进行优化,指令的顺序可能也会被改变.
所以在多线程中可能还是会出状况,虽然这种概率很低,但要是有解决方法为什么不用呢?
那就来看第五种单件模式

五)第五种单件模式

Code


小菜并没有做多少事,只是把private static Singleton _instance;修改为private static volatile Singleton _instance;
volatile关键字有这么大魔力? 对
编译器保证对此_instance的读写操作都不会被优化.

接下来我们来讲个集万千宠爱与一生的单件模式
 

回复:.Net Framework框架源码学习(第二篇 单件模式)

六)第六个单件模式

Code


return Nested.instance保证了原子性.
第一次执行它时
1.为Netsted的静态数据成员Singleton instance分配内存空间,存储空间的值为null
2.执行instance的静态初始语句,由于没有所以跳过
3.执行静态构造函数,执行 instance = new Singleton() 初始化instance
4.返回instance对象

很明显用到的知识还是前面的对象构造顺序,可见有多重要.

到这里单件模式的多种实现都介绍完了.
小菜得出了四字真言: 不容易啊.
 
1  /  1  页   1 跳转

快速回复帖子

标题
禁用 URL 识别
禁用表情
禁用 Discuz!NT 代码
使用个人签名
  [完成后可按 Ctrl+Enter 无刷新发布]  

版权所有 拼吾爱程序人生    Total Unique Visitors:

free hit counter

Powered by Discuz!NT 2.1.202   Copyright © 2001-2008 Comsenz Inc. 鄂ICP备07500843号
返顶部