每天一个设计模式,让架构师远离:单例模式





3.00/5 (4投票s)
关于单例设计模式的文章系列。
引言
这是一个关于设计模式的短篇系列文章(文章短,系列长)。据我理解,设计模式是指针对给定情况的一对问题和解决方案。
让我们从计算领域举个例子。
问题和情况:我想搜索一个数组。已知该数组已排序。
解决方案:使用二分查找,因为它会更快。
同样,一个设计模式的例子可以是……
问题和情况:我希望我的系统中只有一个给定对象的实例。这是必需的,因为该类代表我的日志文件,并且将从多个源访问。因此,为了避免任何竞态条件,我希望只有一个类的实例。在这种情况下,因为存在一个对象,所以使用static
类是没有意义的。
解决方案:使用单例模式。
为什么这个系列文章会与众不同
网络上有无数关于设计模式的文章,那么为什么有人要读这篇文章呢?原因是,在本系列文章中,我将尽量做到更实用而非抽象,并且将以.NET C# 为驱动,甚至利用语言特性来帮助实现这些模式。对主题的处理将更侧重于示例,而非抽象。此外,我们将做那些有意义的事情,如果代码是解释事物的最佳方式,那么就是代码,如果需要复杂的有限状态机,那么就是有限状态机。
模式通常分为设计模式和架构模式。设计模式通常处理局限于应用程序子组件或用例的问题,例如如何实现我的分层帮助。它们的应用提供了一套现成的、可以编码的类型。架构模式的范围更大,并为项目提供指导。它们最终提供了设计将要进行的蓝图。
设计模式类型
根据《设计模式:可复用面向对象软件开发》一书(GOF),模式可以分为:
创建型模式 – 创建型模式讨论如何创建一个新对象,在哪里可以在代码中写 `new`?它们允许我们执行的一些魔术包括使用单例模式只创建一个类的实例;允许使用原型模式创建重量级对象等等。
结构型模式 – 结构型模式讨论如何创建一个对象,是否应该使用继承、组合。换句话说,如何组合我的元素来创建一个更好的类?它们允许我们做的一些魔术包括使用装饰器模式动态创建一个具有类和子类层次结构的类的对象,使用享元模式创建大量相似的对象等等。
行为型模式 – 这些模式处理对象如何通信,或者换句话说,如果我想调用一个对象的某个方法,我该如何做?它们允许进行一些巧妙的魔术,比如在不改变类代码的情况下给类添加一个新方法。例如,在请求提供特定逻辑的具体实现时,给出一个通用算法,比如排序需要两个循环,但 `<` 符号必须由具体的实现提供。
在本系列中,我们将介绍 GOF 模式,然后介绍 GOF 之外的其他模式,例如“复活”。
每日模式:单例模式
这是一个创建型模式——它涉及我如何创建一个新对象。单例模式保证在整个执行上下文或应用程序域中只会创建一个类的实例。
单例模式与 static
不同,后者没有实例。
如何在没有单例的情况下实现?
首先,让我们尝试在不使用单例模式的情况下创建和维护类的单个实例。
public static MyClass myClass = null;
public class MyClass()
{
//Some logic
}
public class MyUsageClass
{
if(myClass == null)
{
myClass = new MyClass();
}
}
这种方法存在一些问题。例如,如果创建对象时我需要维护同步锁,那么这种创建的逻辑将分散在各个地方。如果我想在实例创建之前和之后运行一些特殊代码等等。
如何在 .NET 中实现
public class MyClass
{
private static MyClass myClass = null;
private MyClass()
{
//Any intialization code like open log file
}
public static MyClass GetInstance()
{
if(MyClass.myClass == null)
{
MyClass.myClass = new MyClass();
}
return MyClass.myClass;
}
public static MyClass GetInstance
{
get
{
if (MyClass.myClass == null)
{
MyClass.myClass = new MyClass();
}
return MyClass.myClass;
}
}
}
使用单例模式的方式使生活变得简单一些。存在一个由类自身管理的类的 static
实例。
为了确保没有人可以实例化该类,我们将构造函数设为 private
。存在一个名为 GetInstance
的方法,它在第一次使用时创建实例并返回该类。属性 GetInstance
也完成了类似的工作。因此,由设计者决定使用上述技术中的哪一种。
如果需要使构造函数成为线程安全的,则可以使用以下结构的类的代码。
object locker = new object();
private MyClass()
{
lock (locker)
{
//Any initialization code like open log file
}
}
构造函数仍然负责创建一个好的实例,并完成所有初始化。
如何传递参数?
我曾经就单例模式做过一个演讲,有人问我,如果在实例化类时需要传递参数怎么办。如果需要将参数传递给实例化逻辑,那么这不是单例模式的适用场景。因为该类依赖于一些外部条件,所以单个实例无法满足其工作需求。
单例模式所需的任何属性都应通过部署时配置提供。
单例模式的一些使用场景
单例模式可以在各种场景中使用,其中一些场景包括表示物理资源,如日志文件、网卡等。
历史
- 2010 年 12 月 2 日:首次发布