创建型模式
单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的“单一实例”的需求时才可使用。
Singleton模式主要作用是保证在应用程序中,一个类Class只有一个实例存在。 在很多操作中,比如建立目录 数据库连接都需要这样的单线程操作。还有, singleton能够被状态化; 这样,多个单态类在一起就可以作为一个状态仓库一样向外提供服务,比如,你要论坛中的帖子计数器,每次浏览一次需要计数,单态类能否保持住这个计数,并且能synchronize的安全自动加1,如果你要把这个数字永久保存到数据库,你可以在不修改单态接口的情况下方便的做到。另外方面,Singleton也能够被无状态化。提供工具性质的功能,Singleton模式就为我们提供了这样实现的可能。使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于垃圾回收(garbage collection)。
我们常常看到工厂模式中类装入器(class loader)中也用Singleton模式实现的,因为被装入的类实际也属于资源。
public class Singleton {
private Singleton(){}
//在自己内部定义自己一个实例,是不是很奇怪?
//注意这是private 只供内部调用
private static Singleton instance = new Singleton();
//这里提供了一个供外部访问本class的静态方法,可以直接访问
public static Singleton getInstance() {
return instance;
}
}
public class Singleton {
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if (instance==null)
instance=new Singleton();
return instance; }
}
让我们先花点时间分析一下此代码。 该简单类有一个成员变量。 注意,构造函数是受保护的,并且只有公共方法才是实例方法。 在实例方法实现中,有一个控制块 (if),它检查成员变量是否已初始化,如果没有的话,则创建一个新实例。 控制块中这种惰性初始化意味着仅在第一次调用 Instance() 方法时初始化或创建 Singleton 实例。 对于很多应用程序,这种方法效果很好。 但对于多线程应用程序,这种方法证明具有潜在危险的副作用。 如果两个线程同时进入控制块,则可能会创建该成员变量的两个实例。 要解决这一问题,您可能想只将重要部分放在控制块周围以确保线程安全。 如果您这样做,则将对实例方法的所有调用进行序列化处理,并且可能会对性能产生不利影响(取决于应用程序)。 正是由于这个原因,创建了此模式的另一个版本,它使用某种称为双重检验机制的功能。
以 C# 编码的双重检验锁定
// Port to C# class Singleton { public static Singleton Instance() { if (_instance == null) { lock (typeof(Singleton)) { if (_instance == null) { _instance = new Singleton(); } } } return _instance; } protected Singleton() {} private static volatile Singleton _instance = null; }
此处,我们替换了锁定关键字来执行关键部分块,使用 typeof 操作并添加 volatile 关键字,以确保没有对代码进行优化程序重新排序。 虽然此代码或多或少是 GoF Singleton 模式的直接移植,但它可达到我们的目的,并且我们可获得所需的行为。 此代码还说明了将 C++ 移植到 Java 和将 Java 移植到 C# 代码的一些相似之处和主要差别。 但是,正如任何代码移植一样,通常目标语言或平台的一些优点可能在移植过程中失去。 需要做的就是对代码重构,以便利用新目标语言或平台的功能。
在前面的每个代码示例中,Singleton 的原始实现随时间的推移而发生变化,以解决在每个新模式实现中发现的问题。 一些问题(例如,线程安全)要求对大多数实现进行更改,以满足在目前应用程序中日益增长的需要并解决演变发展问题。 .NET 在应用程序开发中提供了一个演变步骤。 可以在“框架”级别解决前面示例中出现的很多亟待解决的问题,而不是在实现级别解决。 虽然上一个示例显示了一个使用 .NET 框架和 C# 的有效 Singleton 类,但只需更好地利用 .NET 框架本身就可以大大简化此代码。 以下示例使用 .NET,它是一个松散地基于原始 GoF 模式的最小限度的 Singleton 类,并且仍然可获得类似的行为。
.NET Singleton 示例
// .NET Singleton sealed class Singleton //密封类 { private Singleton() {} public static readonly Singleton Instance = new Singleton(); }
此版本已大大简化并且更加直观。 它仍然是 Singleton 吗? 让我们看一下更改了哪些内容,然后再做决定。 我们修改了要密封的类本身(该类密封后是不可继承的),删除了惰性初始化代码,删除了 Instance() 方法,并且对 _instance 变量做了大量的修改。 对 _instance 变量所做的更改包括修改对公共方法的访问级别,将变量标记为只读,以及在声明时初始化该变量。 此处,我们可以直接定义所需的行为,而不关心实现的潜在有害的副作用。 那么,使用惰性初始化有什么优点以及使用多个线程有什么危险呢? 在 .NET 框架中内置了所有正确的行为。 我们真正关心的是在第一次(在该情况下)调用实例属性中创建该实例,还是在此调用之前创建该实例的,并且类中的静态变量是否有已定义的初始化顺序。 对于 .NET 框架,这就是我们获取的行为。 在 JIT 过程中,当(且仅当)任何方法使用静态属性时,“框架”将初始化此静态属性。 如果没有使用该属性,则不会创建实例。 更准确地说,在 JIT 过程中发生的事情就是,在任何调用方使用该类的任何静态成员时构造和加载该类。 在这种情况下,结果是相同的。
那么,线程安全初始化呢? “框架”也解决了这一问题。 “框架”内部保证静态类型初始化的线程安全。 换句话说,在上面的示例中,只创建一个 Singleton 类实例。 还要注意,用于保存类实例的属性字段称为实例。 此选项更好地说明了,在本文中的讨论过程中,此值是类的实例。 在“框架”本身中,虽然使用的属性名称称为值,但有多个类使用此类型的 Singleton。 概念完全相同。
对类所做的其他更改意味着禁止划分子类。 添加密封类修饰符可确保不会将该类划分为子类。 GoF Singleton 模式详细介绍了试图对 Singleton 划分子类所产生的问题,该划分通常并不是小事。 在大多数情况下,可以很容易地开发没有父类的 Singleton,并且添加划分子类功能会增加通常根本不需要的新的复杂性级别。 随着复杂性的提高,测试、培训和文档编制等所需的时间也会增加。 通常,除非绝对必要,否则您不希望提高任何代码的复杂性。
让我们看一下如何使用 Singleton。 使用我们最初的计数器的有关动机的概念,我们可以创建一个简单的 Singleton 计数器类并说明我们将如何使用它。
相应的类实现代码以及示例客户端使用如下所示。
示例 Singleton 使用
sealed class SingletonCounter { public static readonly SingletonCounter Instance = new SingletonCounter(); private long Count = 0; private SingletonCounter() {} public long NextValue() { return ++Count; } } class SingletonClient { [STAThread] static void Main() { for (int i=0; i<20; i++) { Console.WriteLine("Next singleton value: {0}", SingletonCounter.Instance.NextValue()); } } }
此处,我们还创建了一个 Singleton 类来维护具有 long 类型的增量计数。 客户端是一个简单的控制台应用程序,它显示计数器类的 20 个值。 虽然此示例极其简单,但它却说明了如何使用 .NET 来实现 Singleton,然后将其用在应用程序中。
小结
Singleton 设计模式是一个非常有用的机制,可用于在面向对象的应用程序中提供单个对象访问点。 无论使用的是什么实现,该模式提供一个大家所熟知的概念,以便其在设计和开发小组之间方便地进行共享。 但是,正如我们所发现的一样,注意到这些实现有多大差异及其潜在的副作用也是非常重要的。 .NET 框架为模式实现者在设计所需的功能类型方面提供了很大的帮助,实现者无需处理本文中所讨论的很多副作用。 在正确实现后,可以证实模式的最初目的的有效性。
设计模式是非常有用的软件设计概念,可使小组将重点放在提供最佳类型的应用程序上,而不考虑它们是什么应用程序。 关键在于正确而有效地使用设计模式,目前有很多关于将设计模式用于 Microsoft .NET 方面的 MSDN 系列文档,其中介绍了如何正确而有效地使用设计模式。