说明:
我正在撰写《面向对象的艺术——.NET Framework 4.0技术剖析与应用》(暂名)一书,会陆续将一些章节发到我的博客上。
作者本人拥有所有的版权。允许自由阅读和转载这些文章,但任何个人与机构不能将其用于商业目的。
补充说明:
我在CSDN博客及下载频道发布技术文章与相应教学资源,是长期的一贯的,其目的为推动普及软件技术知识。
非常欢迎网友指出我发布的文章中的疏漏,并对我的写作给出中肯与具体的建议,这也是我发布这些文章和资源的目的之一。
然而,对于那些主观臆断的,无中生有的,纯粹灌水甚至带有人身攻击的回复,那就免了罢。请这些闲人另找别处发泄去,我这不欢迎你们。
金旭亮
==================================================
《面向对象的艺术》 之
对象是以类为模板创建的实例,默认情况下,一个类可以创建无数个对象。
现在问题来了:如果我想限制某个类最多只能创建一个对象,那该怎么办?
这有N种方式可以实现。本节示例解决方案为OnlyYou,包容几个控制台应用程序。
如果我们在创建对象前先检查一下用于引用对象的对象变量是否为空引用,就可限制对象的创建个数(参见示例项目OnlyYou1)。
示例项目中,OnlyYou是一个只允许创建一个对象的类。
class OnlyYou
{
}
在Program类中定义一个静态字段和静态方法:
static OnlyYou OnlyYouObject = null; //用于引用OnlyYou对象
//创建OnlyYou对象,只保证创建一个
static OnlyYou GetOnlyYouObject()
{
if (OnlyYouObject == null)
OnlyYouObject=new OnlyYou();
return OnlyYouObject;
}
静态方法GetOnlyYouObject()保证了在整个项目中只能创建一个OnlyYou对象。
以下是放置在Main()中的测试代码。
static void Main(string[] args)
{
OnlyYou One, Two;
One = GetOnlyYouObject();
Two = GetOnlyYouObject();
//检查一下两个对象变量是否引用同一对象
Console.WriteLine(One == Two); //true
}
}
上述“检测对象变量是否为空引用”虽然可以保证整个项目中只能创建一个OnlyYou的对象,但是这种方式必须要在OnlyYou类的外部书写代码,并不方便。
另一种更好的方式是在OnlyYou类的内部设置一个对象计数器,通过检查此计数器的值来确定是否要新建一个对象。
以下是修改过的OnlyYou代码:
class OnlyYou
{
private static int ObjectCounter = 0;//对象计数器
private static OnlyYou OnlyYouObject = null;
public static OnlyYou GetOnlyYouObject()
{
if (ObjectCounter == 0)
{
OnlyYouObject = new OnlyYou();
ObjectCounter++;
}
return OnlyYouObject;
}
}
外界使用OnlyYou.GetOnlyYouObject()方法获取创建的唯一对象的引用。
这个方法看上去不错,但还是有点问题,那就是OnlyYou的使用者完全可以绕开OnlyYou.GetOnlyYouObject()方法,直接使用new关键字创建N个OnlyYou对象。
解决方法还是有的,这就是“将类的构造函数私有化”。
当一个类的构造函数设置为private之后,外界将无法使用new关键字来创建这个类的对象。
修改后的OnlyYou类:
class OnlyYou
{
//构造函数私有,外界不能用new直接创建对象
private OnlyYou()
{
}
//用于保存“独生子”的静态对象变量
private static OnlyYou OnlyYouObject = null;
public static OnlyYou GetOnlyYouObject ()
{
if (OnlyYouObject == null) //对象未创建,则创建对象
OnlyYouObject =new OnlyYou ();
//向外界返回已创建对象的引用
return obj;
}
}
类OnlyYou的关键在于两点:
(1)构造函数私有,则外界无法用new关键字直接创建对象;
(2)提供一个公有静态方法向外界返回已创建对象的引用。
提示:
示例项目OnlyYou的设计方案其实是一种经典的设计模式,名为Singleton(中文译名为“单件”)。设计模式是对一些软件精巧设计方案的总结,设计模式理论是面向对象理论的重要组成部分之一。感兴趣的读者可以去读一本经典的设计模式书籍——《设计模式:可复用面向对象软件的基础》,由Erich Gamma 等四名博士所著。
应用Singleton设计模式的OnlyYou看上去很不错,它完美地实现了“只能创建一个对象”的预期目标。
然而,这其中还是有陷阱的。那就是我们还没有考虑到多线程的问题。
当有多个线程同时调用OnlyYou.GetOnlyYouObject()方法时,由于Windows操作系统采用抢先式线程调度策略,完全有可能一个线程还未执行完OnlyYou.GetOnlyYouObject()方法就被剥夺了CPU,如果它这时刚刚完成new OnlyYou()的工作,还未来得及更新OnlyYouObject变量,而另一个投入运行的线程在执行OnlyYou.GetOnlyYouObject()方法时会发现OnlyYouObject==null,这将导致一个新的OnlyYou对象被创建。
由于可能发现这种情况,我们就说OnlyYou类不是“线程安全(thread safe)”的。
知道了问题所在,解决起来就比较简单了:
class OnlyYou
{
//构造函数私有,外界不能用new直接创建对象
private OnlyYou()
{
}
//用于保存“独生子”的静态对象变量
private static OnlyYou OnlyYouObject = null;
public static OnlyYou GetOnlyYouObject()
{
lock(typeof(OnlyYou)) //锁定整个类型
{
if (OnlyYouObject == null) //对象未创建,则创建对象
OnlyYouObject=new OnlyYou ();
//向外界返回已创建对象的引用
return OnlyYouObject;
}
}
}
通过锁定OnlyYou类型,实现了多线程环境下的OnlyYou.GetOnlyYouObject()方法串行执行。
提示:
在我们这个例子中,由于OnlyYou中只有一个静态方法,也没有任何的公有实例方法,因此,锁定整个类型问题不大。
然而,如果某个类中包容了多个静态和实例方法,而这个类又频繁地被多个线程访问,则锁定整个类型将有可能带来严重的性能损失,因为所有对此类方法的调用都必须串行执行。
解决方法是“提供专用的用于线程同步的对象或类型”。
例如,可以定义一个“没什么用的”类型专用于线程同步:
class UseOnlyForLock
{
}
在需要线程同步的地方这样调用:
lock(typeof(UseOnlyForLock))
{
//线程同步代码……
}
这个方法多用于多线程同步静态方法。
在包容多个方法的类内部,还可以临时增加专用于同步的“没有什么用”的实例成员,专用于多线程同步实例方法:
class SomeClass
{
private object SyncObject=new object();
void SomeMethod()
{
lock(SyncObject)
{
//线程同步代码……
}
}
}
综合应用上述两种方法,可以有效地避免锁定一个“拥有很多方法和属性”的类型所带来的性能损失。
试一试:
如果我想定义一个可以在运行时“动态”指定创建对象个数的类,应该怎么设计?