.NET 设计模式开篇
——.NET 设计模式系列之一
Terrylee,2005 年 12 月 06 日
前言
加入 Design & Pattern 团队有几个月的时间了,惭愧的是从没有写过关于设计模式的随笔,得
到 wayfarer 的同意,把企业库系列的随笔放在了团队的首页上。不是不想去写这样的随笔,也
不是没有时间,自己初学设计模式,去写设计模式的文章,有点班门弄斧的味道。园子里吕震宇
老师的《设计模式系列》和 wayfarer 的《设计之道》堪称设计模式里的经典之作。可是正如 wa
farer 所说的那样,受到发表欲的蛊惑,本着交流就是进步的想法,思考再三,还是决定写这样
的随笔,来对设计模式做一些探索和总结,起名曰“探索设计模式”,有些言过其实,就当是记
录自己学习设计模式的历程吧,不过还是希望能得到各位前辈的指点!
设计模式
设计模式是规则吗?
地上本没有路,走得人多了也就成了路。设计模式如同此理,它是经验的传承,并非体系;是被
前人发现,经过总结形成了一套某一类问题的一般性解决方案,而不是被设计出来的定性规则;
它不像算法那样可以照搬照用。
设计模式是架构吗?
架构和模式应该是一个属于相互涵盖的过程,但是总体来说架构更加关注的是所谓的 High-Leve
l Design,而模式关注的重点在于通过经验提取的“准则或指导方案”在设计中的应用,因此在
不同层面考虑问题的时候就形成了不同问题域上的模式。模式的目标是,把共通问题中的不变部
分和变化部分分离出来。不变的部分,就构成了模式,因此,模式是一个经验提取的“准则”,
并且在一次一次的实践中得到验证,在不同的层次有不同的模式,小到语言实现,大到架构。在
不同的层面上,模式提供不同层面的指导。
设计模式,软件的永恒之道?
这个问题没有答案,有的只是讨论,看一下一位前辈结合建筑学得出的几点心得吧:
和建筑结构一样,软件中亦有诸多的“内力”。和建筑设计一样,软件设计也应该努力疏解系统中
的内力,使系统趋于稳定、有生气。一切的软件设计都应该由此出发。
任何系统都需要有变化,任何系统都会走向死亡。作为设计者,应该拥抱变化、利用变化,而不
是逃避变化。
好的软件只能“产生”而不能“创造”,我们所能做的只是用一个相对好的过程,尽量使软件朝向好
的方向发展。
需要设计模式吗?
答案是肯定的,但你需要确定的是模式的应用是否过度?我得承认,世界上有很多天才的程序员,
他可以在一段代码中包含 6 种设计模式,也可以不用模式而把设计做得很好。但我们的目标是
追求有效的设计,而设计模式可以为这个目标提供某种参考模型、设计方法。
我们不需要奉 GOF 的设计模式为圭臬,但合理的运用设计模式,才是正确的抉择。很多人看过 G
OF 的《Design Patterns》,对这 23 种模式也背得滚瓜烂熟。但重要的不是你熟记了多少个模
式的名称,关键还在于付诸实践的运用。为了有效地设计,而去熟悉某种模式所花费的代价是值
得的,因为很快你会在设计中发现这种模式真的很好,很多时候它令得你的设计更加简单了。
其实在软件设计人员中,唾弃设计模式的可能很少,盲目夸大设计模式功用的反而更多。言必谈
“模式”,并不能使你成为优秀的架构师。真正出色的设计师,懂得判断运用模式的时机。还有
一个问题是,很多才踏入软件设计领域的人员,往往对设计模式很困惑。对于他们来说,由于没
有项目的实际经验,OO 的思想也还未曾建立,设计模式未免过于高深了。其实,即使是非常有
经验的程序员,也不敢夸口对各种模式都能合理应用。[--摘自 wayfare 的设计之道]
后记
关于设计模式的理论性的文章,已经写了很多了,我不想再继续重复抄写下去,仅记录下上面几
段话,用它来作探索设计模式系列的一个开篇吧。[现已更名为.NET 设计模式]
第Ⅱ部分 创建型模式篇
单件模式(Singleton Pattern)
——.NET 设计模式系列之二
Terrylee,2005 年 12 月 07 日
概述
Singleton 模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点。这就提出了一个
问题:如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?客户程序在调用某一
个类时,它是不会考虑这个类是否只能有一个实例等问题的,所以,这应该是类设计者的责任,
而不是类使用者的责任。
从另一个角度来说,Singleton 模式其实也是一种职责型模式。因为我们创建了一个对象,这个
对象扮演了独一无二的角色,在这个单独的对象实例中,它集中了它所属类的所有权力,同时它
也肩负了行使这种权力的职责!
意图
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
模型图
逻辑模型图:
物理模型图:
生活中的例子
美国总统的职位是 Singleton,美国宪法规定了总统的选举,任期以及继任的顺序。这样,在任
何时刻只能由一个现任的总统。无论现任总统的身份为何,其头衔"美利坚合众国总统"是访问这
个职位的人的一个全局的访问点。
五种实现
1.简单实现
1 public sealed class Singleton
2
{
3 static Singleton instance=null;
4
5 Singleton()
6
{
7 }
8
9 public static Singleton Instance
10
{
11 get
12
{
13 if (instance==null)
14
{
15 instance = new Singleton();
16 }
17 return instance;
18 }
19 }
20 }
这种方式的实现对于线程来说并不是安全的,因为在多线程的环境下有可能得到 Singleton 类的
多个实例。如果同时有两个线程去判断(instance == null),并且得到的结果为真,这时两个
线程都会创建类 Singleton 的实例,这样就违背了 Singleton 模式的原则。实际上在上述代码中,
有可能在计算出表达式的值之前,对象实例已经被创建,但是内存模型并不能保证对象实例在第
二个线程创建之前被发现。
该实现方式主要有两个优点:
由于实例是在 Instance 属性方法内部创建的,因此类可以使用附加功能
(例如,对子类进行实例化),即使它可能引入不想要的依赖性。
直到对象要求产生一个实例才执行实例化;这种方法称为“惰性实例化”。
惰性实例化避免了在应用程序启动时实例化不必要的 singleton。
2.安全的线程
1 public sealed class Singleton
2
{
3 static Singleton instance=null;
4 static readonly object padlock = new object();
5
6 Singleton()
7
{
8 }
9
10 public static Singleton Instance
11
{
12 get
13
{
14 lock (padlock)
15
{
16 if (instance==null)
17
{
18 instance = new Singleton();
19 }
20 return instance;
21 }
22 }
23 }
24 }
25
26
这种方式的实现对于线程来说是安全的。我们首先创建了一个进程辅助对象,线程在进入时先对
辅助对象加锁然后再检测对象是否被创建,这样可以确保只有一个实例被创建,因为在同一个时
刻加了锁的那部分程序只有一个线程可以进入。这种情况下,对象实例由最先进入的那个线程创
建,后来的线程在进入时(instence == null)为假,不会再去创建对象实例了。但是这种实现
方式增加了额外的开销,损失了性能。
3.双重锁定
1 public sealed class Singleton
2
{
3 static Singleton instance=null;
4 static readonly object padlock = new object();
5
6 Singleton()
7
{
8 }
9
10 public static Singleton Instance
11
{
12 get
13
{
14 if (instance==null)
15
{
16 lock (padlock)
17
{
18 if (instance==null)
19
{
20 instance = new Singleton();
21 }
22 }
23 }
24 return instance;
25 }
26 }
27 }
28
这种实现方式对多线程来说是安全的,同时线程不是每次都加锁,只有判断对象实例没有被创建
时它才加锁,有了我们上面第一部分的里面的分析,我们知道,加锁后还得再进行对象是否已被
创建的判断。它解决了线程并发问题,同时避免在每个 Instance 属性方法的调用中都出现独
占锁定。它还允许您将实例化延迟到第一次访问对象时发生。实际上,应用程序很少需要这种类
型的实现。大多数情况下我们会用静态初始化。这种方式仍然有很多缺点:无法实现延迟初始化。
4.静态初始化
1 public sealed class Singleton
2
{
3 static readonly Singleton instance=new Singleton();
4
5 static Singleton()
6
{
7 }
8
9 Singleton()
10
{
11 }
12
13 public static Singleton Instance
14
{
15 get
16
{
17 return instance;
18 }
19 }
20 }
21
看到上面这段富有戏剧性的代码,我们可能会产生怀疑,这还是 Singleton 模式吗?在此实现中,
将在第一次引用类的任何成员时创建实例。公共语言运行库负责处理变量初始化。该类标记为
sealed 以阻止发生派生,而派生可能会增加实例。此外,变量标记为 readonly,这意味着只
能在静态初始化期间(此处显示的示例)或在类构造函数中分配变量。
该实现与前面的示例类似,不同之处在于它依赖公共语言运行库来初始化变量。它仍然可以用来
解决 Singleton 模式试图解决的两个基本问题:全局访问和实例化控制。公共静态属性为访问
实例提供了一个全局访问点。此外,由于构造函数是私有的,因此不能在类本身以外实例化
Singleton 类;因此,变量引用的是可以在系统中存在的唯一的实例。
由于 Singleton 实例被私有静态成员变量引用,因此在类首次被对 Instance 属性的调用所引
用之前,不会发生实例化。
这种方法唯一的潜在缺点是,您对实例化机制的控制权较少。在 Design Patterns 形式中,您
能够在实例化之前使用非默认的构造函数或执行其他任务。由于在此解决方案中由 .NET
Framework 负责执行初始化,因此您没有这些选项。在大多数情况下,静态初始化是在 .NET 中
实现 Singleton 的首选方法。
5.延迟初始化
1 public sealed class Singleton
2
{
3 Singleton()
4
{
5 }
6
7 public static Singleton Instance
8
{
9 get
10
{
11 return Nested.instance;
12 }
13 }
14
15 class Nested
16
{
17 static Nested()
18
{
19 }
20
21
internal static readonly Singleton instance = new Singleto
n();
22 }
23 }
24
这里,初始化工作有 Nested 类的一个静态成员来完成,这样就实现了延迟初始化,并具有很多
的优势,是值得推荐的一种实