返回首页 设计模式之创建型模式

简单工厂模式

工厂方法模式

抽象工厂模式

单例模式

原型模式

建造者模式

确保对象的唯一性——单例模式 (一)

单例模式的动机

对于一个软件系统的某些类而言,我们无须创建多个实例。举个大家都熟知的例子—— Windows 任务管理器,如图所示,我们可以做一个这样的尝试,在 Windows 的“任务栏”的右键弹出菜单上多次点击“启动任务管理器”,看能否打开多个任务管理器窗口?(注:电脑中毒或私自修改 Windows 内核者除外)。通常情况下,无论我们启动任务管理多少次,Windows系统始终只能弹出一个任务管理器窗口,也就是说在一个 Windows 系统中,任务管理器存在唯一性。为什么要这样设计呢?我们可以从以下两个方面来分析:其一,如果能弹出多个窗口,且这些窗口的内容完全一致,全部是重复对象,这势必会浪费系统资源,任务管理器需要获取系统运行时的诸多信息,这些信息的获取需要消耗一定的系统资源,包括CPU资源及内存资源等,浪费是可耻的,而且根本没有必要显示多个内容完全相同的窗口;其二,如果弹出的多个窗口内容不一致,问题就更加严重了,这意味着在某一瞬间系统资源使用情况和进程、服务等信息存在多个状态,例如任务管理器窗口 A 显示“CPU 使用率”为 10%,窗口 B 显示“CPU 使用率”为 15%,到底哪个才是真实的呢?这纯属“调戏”用户,给用户带来误解,更不可取。由此可见,确保 Windows 任务管理器在系统中有且仅有一个非常重要。

Windows任务管理器

回到实际开发中,我们也经常遇到类似的情况,为了节约系统资源,有时需要确保系统中某个类只有唯一一个实例,当这个唯一实例创建成功之后,我们无法再创建一个同类型的其他对象,所有的操作都只能基于这个唯一实例。为了确保对象的唯一性,我们可以通过单例模式来实现,这就是单例模式的动机所在。

单例模式概述

下面我们来模拟实现 Windows 任务管理器,假设任务管理器的类名为 TaskManager,在 TaskManager 类中包含了大量的成员方法,例如构造函数 TaskManager(),显示进程的方法 displayProcesses(),显示服务的方法 displayServices() 等,该类的示意代码如下:

class TaskManager  
{  
     public TaskManager() {……} //初始化窗口  
     public void displayProcesses()  {……} //显示进程  
     public void  displayServices() {……} //显示服务  
     ……  
}  

为了实现 Windows 任务管理器的唯一性,我们通过如下三步来对该类进行重构:

(1) 由于每次使用 new 关键字来实例化 TaskManager 类时都将产生一个新对象,为了确保 TaskManager 实例的唯一性,我们需要禁止类的外部直接使用 new 来创建对象,因此需要将 TaskManager 的构造函数的可见性改为 private,如下代码所示:

private TaskManager() {……}    

(2) 将构造函数改为 private 修饰后该如何创建对象呢?不要着急,虽然类的外部无法再使用 new 来创建对象,但是在 TaskManager 的内部还是可以创建的,可见性只对类外有效。因此,我们可以在 TaskManager 中创建并保存这个唯一实例。为了让外界可以访问这个唯一实例,需要在 TaskManager 中定义一个静态的 TaskManager 类型的私有成员变量,如下代码所示:

private static TaskManager tm = null;

(3) 为了保证成员变量的封装性,我们将 TaskManager 类型的 tm 对象的可见性设置为 private,但外界该如何使用该成员变量并何时实例化该成员变量呢?答案是增加一个公有的静态方法,如下代码所示:

public static TaskManager getInstance()
{
if (tm == null)
{
tm = new TaskManager();
}
return tm;
}

在 getInstance() 方法中首先判断 tm 对象是否存在,如果不存在(即 tm == null),则使用 new 关键字创建一个新的 TaskManager 类型的 tm 对象,再返回新创建的 tm 对象;否则直接返回已有的 tm 对象。

需要注意的是 getInstance() 方法的修饰符,首先它应该是一个 public 方法,以便供外界其他对象使用,其次它使用了 static 关键字,即它是一个静态方法,在类外可以直接通过类名来访问,而无须创建 TaskManager 对象,事实上在类外也无法创建 TaskManager 对象,因为构造函数是私有的。

思考

为什么要将成员变量 tm 定义为静态变量?

通过以上三个步骤,我们完成了一个最简单的单例类的设计,其完整代码如下:

class TaskManager
{
     private static TaskManager tm = null;
     private TaskManager() {……} //初始化窗口
     public void  displayProcesses() {……} //显示进程
     public void  displayServices() {……} //显示服务
     public static TaskManager getInstance()
     {
        if (tm == null)
        {
            tm = new TaskManager();
        }
        return tm;
    }
   ……
}

在类外我们无法直接创建新的 TaskManager 对象,但可以通过代码 TaskManager.getInstance() 来访问实例对象,第一次调用 getInstance() 方法时将创建唯一实例,再次调用时将返回第一次创建的实例,从而确保实例对象的唯一性。

上述代码也是单例模式的一种最典型实现方式,有了以上基础,理解单例模式的定义和结构就非常容易了。单例模式定义如下:

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。

单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

单例模式是结构最简单的设计模式一,在它的核心结构中只包含一个被称为单例类的特殊类。单例模式结构如图所示:

单例模式结构图中只包含一个单例角色:

Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的 getInstance() 工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个 Singleton 类型的静态对象,作为外部共享的唯一实例。