作者:KiddLee 来源:博客园 酷勤网收集 2007-09-03
这是我写模式设计的第二篇,首先来说说设计模式的分类。
基本的23种设计模式从目的上可分为三种:
1、 创建型(Creational)模式:负责对象创建。
2、 结构型(Structural)模式:处理类与对象间的组合,可以解决一些继承依赖性的问题
3、 行为型(Behavioral)模式:类与对象交互中的职责分配,可以解决组件间如何和交互,隔离变化。
下面来说说单件模式:
首先说说单件模式产生的动机,也就是为什么会出现单件模式。有一些类在系统中只存在一个实例才能确保他们的逻辑正确性以及良好的效率。这时我想到我遇到的一个问题。我曾经遇到一个WinForm程序,运行后出现一个登陆框,输入用户名密码后点击登陆,然后显示一个登陆后的界面。但是点击登陆后,程序要做一些操作,由于这段操作用时相对较长,在不经意时,我有点击了一次登陆按钮,最后出现了两个对话框。如:我现在有两个Form窗体Form1和Form2。Form1上有一个按钮用来打开Form2并隐藏自己。我们可以这样写:
private void button1_Click(object sender, System.EventArgs e)
{
Form2 form = new Form2();
form.Show();
this.Hide();
}
如果我们在显示Form2前由一些比较耗时的操作。如:我们让线程的沉睡10秒在显示Form2,当我们在线程沉睡时继续点击Form1上的Button,有可能就会出现两个Form2的窗体。(我试过可以出现两个Form2,如果你有心试但没事出来别拿西红柿砍我,哈哈)
private void button1_Click(object sender, System.EventArgs e)
{
Thread.Sleep(10000);
Form2 form = new Form2();
form.Show();
this.Hide();
}
这种情况出现不能怪客户多点了一下,也不能说是编译器不够智能,应该是我们程序上的Bug,我想这种情况用单件模式应该可以解决。
单件模式的使用意图就是:保证一个类仅有一个实例,并提供一个该实例全局的访问点(这句话当然不是我先说的,是引用Gof在《设计模式》中的一句话)
那类的设计者如何绕过常规的构造器来实现单件模式呢?下面就来谈谈单件模式的实现。
单件模式在结构上使用了景泰方法来约束构造器(也就是构造函数)创建对象。
在单线程的情况下:私有化构造函数,使类的使用者调用不到这个构造函数来new一个实例。类型中可以自己new一个实例。类中创建一个静态私有变量和Static公有属性。在公有属性中实现此类的实例化。这样在第一次请求时创建此对象。代码如下:
class Singleton
{
private static Singleton _instance;
private Singleton(){}
public static Singleton f_Instance
{
get
{
if(_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
}
我在main函数中写入如下程序来查看一下这样写是否有效:
static void
{
Singleton t1 = Singleton.f_Instance;
Singleton t2 = Singleton.f_Instance;
Console.Write(object.ReferenceEquals(t1,t2));
Console.Read();
}
控制台显示为True,开来还是有效的。当然在Main中我也试过这样写:Singleton t1 = new Singleton(),编译时告诉我Singleton()不可访问(当然,人家是私有的,不是自家人当然不见)
这种单线程下的单件模式有几点要注意:
1、 构造器私有化(如果要此类被继承,可以用protected声明构造器)
2、 不要支持IClinieable接口,因为会导致多个对象实例的出现
3、 不能支持序列化
4、 单件模式只考虑了对象创建的管理,没有考虑对象的销毁管理(创建自己的对象,销毁的事交给垃圾回收器吧)
5、 不能应对多线程环境,因为会导致多个对象实例的出现
那在多线程下如何实现呢?代码如下:
class SingletonMuli//多线程Singleton模式
{
private static volatile SingletonMuli _instance; //volatile是为了让编译器对此代码编译后的位置不进行调整
private SingletonMuli(){}
private static object lockHelper = new object(); //辅助器,不参与对象构建
public static SingletonMuli f_Instance
{
get
{
if(_instance == null)
{
lock(lockHelper)
{
if(_instance == null) //双检查
{
_instance = new SingletonMuli();
}
}
}
return _instance;
}
}
}
当然还有一些更简单的实现方法,如:
class Singleton1//可以用在多线程环境
{
public static readonly Singleton1 _instance = new Singleton1();
private Singleton1(){}
}
其中要提到的是在_instance私有字段的实例化叫做“内联初始化”。内联初始化是指在声明时。
实际上面的代码上相当于如下代码:
Public static readonly Singleton1 _instance;
Static Singleton() //静态构造函数
{
_instance = new Singleton(); //私有构造器
}
Private Singleton(){}
内联初始化时会先执行静态构造器,如果没有静态构造函数,系统会默认一个。在访问此静态字段时执行静态构造器生成。静态构造器保证了在多线程时只有一个线程执行,自动加锁。
当然,第二种实现方式也有一些缺点,如:静态构造器必须是私有的、无参的。不过也可以用其他的方式解决这类问题。如可以用方法属性实现扩展或修改私有构造器。
现在我们可以回来看看我开始说的那两个Form的问题,我们现在可以这样实现:
private static Form2 form;
private void button1_Click(object sender, System.EventArgs e)
{
Thread.Sleep(10000);
object lockhelp = new object();
if(form == null)
{
lock(lockhelp)
{
if(form == null)
{
form = new Form2();
form.Show();
}
}
}
this.Hide();
}
这样问题就解决了(我是没有点出来第二个Form2,如果那位点出来了,给我发Email,我请她/他在天津的烤鸭)
单件模式实际上是利用控制对象创造过程来控制对象的创造个数的方法,我们可以对其进行扩展,不是让他只生成一个对象,可以让他只生成几个对象,这样可以实现对象池。
单件模式的核心是:如何控制用户使用new对一个类的实例构造器的任意调用。
FeedBack:
你知道为什么要这样写吗?
而且,能显示两个窗体,正是EventHandler注册了多个委托实例的原因,
你修改后的button1_Click也并不是单件模式的应用.
望赐教
并不是大多数类都要用Singleton模式的,应该是大多数类都不用的,而只是那些少数资源比较昂贵的类才会用!
修改后的代码我没有拷贝完全,private static object lockHelper = new object(); 这句代码声明了一个辅助器,这是在下面为了锁住资源使用的。出现两个窗体是因为在程序运行时点击了两次Button1。我对Form程序的修改是按照多线程单件模式的代码事例写的。
单件模式本质就是控制对象的构建,如果你把对象构建的方法给了类的使用者,你就无法控制类对象的构建个数,要控制类对象的构建个数,就要自己掌握类的构建方法,类的构建方法不被其他类使用者使用,所以,类的构建方法私有化可以达到限制的目的
设计模式是一种思想,他也在不断的扩展,正像我后面说的对象池,他就可以认为是一种单件模式的扩展。我有时间的话会再写一篇利用单件模式的思想或扩展来实现对象池的代码。有时间的话,请多多关注,多提意见
您的代码里还在用form = new Form2();
不也是调用了公有构造函数??
难道在外面加了几个lock就是Singleton?
您的例子只能说是您在Form1中以Singleton的方式使用了Form2,而不能说Form2实现了Singleton模式………………
如果真的使用了Singleton,应该写form = Form2.Instance;才对…………
设计模式是思想,而不是固定的代码片断,使用设计模式是为了解决问题,而不是为了使用设计模式而使用设计模式。单件模式的实现也不是就我上面写的那几种。有很多种写法。而且这种思想本身也在发展。就我上面写的那段WinForm的程序,我不用非要说明这是单件模式,我只想实现我的功能就可以。
TerryLee曾和我说:设计模式就像打太极拳,要融会贯通,而不是循规蹈矩,当然很高兴你跟我讨论这个问题,不知这样的解释你是否满意 回
针对这个功能的情况是没问题的,因为无论你如何点击或快速都是基于单线程操作,并没有出现并发的情况.
如果楼主没有修改代码的情况,那代码应该不是线程安全的.
按照你说的“只有少数资源比较昂贵的类才会用”的话,我就更不明白了,既然Singleton模式有这么多好处,比如可以减少资源的浪费和代码输入量的减少,为什么不只要能用的地方都用呢?这样不是更好吗?
望 赐教! 其实就是这个问题我搞不懂,难道这个模式还是有缺点的(当然我是指在能用的情况下)
一下问这么多问题啊,呵呵,我慢慢来解释吧:)
1.“只有少数资源比较昂贵的类才会用”比如说NHibernate中的ISession实例,如果说再多线程情况下都去ISession实例的话,开销会非常之大,所以这里就要使用Singleton模式。
我一再说的是并不是所有的类都要用Singleton模式,比如说实体类,就无法使用。
2.关于模式的优缺点问题,可以说一句,设计模式是为了灵活性和扩展性、可维护性而存在的,并不是为了提高性能,相反是牺牲了性能来换取灵活性。Singleton模式也有它自身的缺点,实例数量虽然减少了每次对象请求引用时都要检查是否存在类的实例,还是需要一些开销,所以这就是需要一个权衡的过程!
3.你说的那段代码是指下面这段吗?
======================================================
class Singleton
{
private static Singleton _instance;
private Singleton(){}
public static Singleton f_Instance
{
get
{
if(_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
}
====================================================
这段代码是Single模式的一个简单实现,在单线程下是没有问题的,但是如果是多线程,就会造成可能创建的实例不是一个,会有并发操作的问题,更多细节可以参考我写的设计模式的文章:
http://terrylee.cnblogs.com/archive/2005/12/09/293509.html
4.还有一点,Singleton模式减少代码输入量的优点从何谈起呢?这一点我没这样说过,也没体会到它会减少代码输入量。
最后,个人认为Singleton模式仅仅是提供了保证类只有一个实例的这样一种机制!具体使用那种实现方法,根据具体情况来定。比如说有人说Doubl-Check有缺点,不能使用,但事实上在某些情况下还是可以使用,下面这段代码出自于Enterprise Library2.0中:
private static object sync = new object();
private static volatile LogWriter writer;
//得到创建LogWriter的工厂实例
private static LogWriterFactory factory = new LogWriterFactory(ConfigurationSourceFactory.Create());
public static LogWriter Writer
{
get
{
if (writer == null)
{
lock (sync)
{
if (writer == null)
{
try
{
//得到LogWriter对象
writer = factory.Create();
}
catch (ConfigurationErrorsException configurationException)
{
TryLogConfigurationFailure(configurationException);
throw;
}
}
}
}
return writer;
}
}
最常见的就是单线程和多线程下的处理,如果对这些问题没有搞清楚的情况下使用是很危险的.
如果我没看错源码的情况下NHibernate中的ISession应该不是采用单件模创建的.
晕,可能是我没有说清楚,NHibernate源码中的ISession是没有用Singleton模式,我的意思是在我们自己创建ISession实例时,难道你也不用Singleton模式??
SessionFactory我往往是采有单件模式保存.
而ISession那看情况来定的,在Win下我通常会采用Singleton模式,但在web下我就不会选择Singleton模式的.
模式这东西我还是觉得在代码重构过程中掌握会有更多的收益.
上文上的代码输入的减少我是指
不用每个地方都需要Class1 class=new Class1()这样写了,后来想想确实没有代码量输入的减少,呵呵
因为我是做web的,基本上都是单线程,不用考虑多线程。所以说如果能用的我觉得都应该用。
我想当然的觉得“每次对象请求引用时都要检查是否存在类的实例”,比每次new一个实例应该对系统开销要小的多! 当然我是想当然的,没有试验过 不知道对不对
代码质疑的是 Doubl-Check 这块,我本以为微软提供的webcast应该不会有什么问题的,呵呵
从重构到模式是最好的使用设计模式的方法!
我想这个问题的讨论该结束了,再讨论下去就会有人说我们无聊了,哈哈:-)
这次的讨论很愉快,以后多多交流!

