设计模式(Design Pattern) —— 单例模式(Singleton) (一)

声明: 本文中有部分理论和思想源于《C++设计新思维:泛型编程与设计模式之应用》一书,向作者 Andrei Alexandrescu 大师致敬!


写多了代码,如果要想代码以后便于维护,系统结构清晰, 便于扩展,必然要使用一些能够复用的、已经经过实践证明是成功的、大家约定俗成的方法; 自从多年前 “四人帮”(Gang of Four) 那四个老爷子总结出来23中模式后,大家一致探讨、沿用至今,被实践证明是最好用的,最成功的。

单例模式(Singleton)是其中最常用、最实用的一种模式。

什么是单例(Singleton)呢?
Gof说:“保证一个class只有一个实例(instance),并为它提供一个全局访问点(global access point)”.

如此看来, Singleton像是一种被改进过、包装过的全局变量。 它比全局变量的改进点在于,你无法产生第二个具有 Singleton 形态的对象。


单例模式的误区

那么如此看来, 静态数据 + 静态方法 = 单例  ??
    class Display{

    //...

    void powerOff();

};


class Keyboard {

    // ...

    void powerOff();

};


class Mouse{

    //...

    void powerOff();

};


class MyOnlyComputer{

public:

    static void powerOff()

    {

        mouse.powerOff();

        keyboard.powerOff();

        display.powerOff();

        // ...

    }

    // ...

private:

    static Display  display;

    static Keyboard keyboard;

    static Mouse    mouse;

};


//implementation

Display  MyOnlyComputer::display;

Keyboard  MyOnlyComputer::keyboard;

Mouse    MyOnlyComputer::mouse;


乍一看, 上面这个例子()确实也做到了 Singleton 能做到的, 但是这个做法有很多缺陷;

首先, 静态函数不能为虚函数,不能被继承,所以,很难让外界扩展。 例如以后 MyOnlyComputer 增添配置, 加了一个视频设备 Camera 后, 关机时就不能关掉它了, 带来了隐患;

其次, 无法将数据集中管理,各个静态数据的管理工作带来了麻烦, 初始化和清理, 不能保证顺序、关系等;

也无法保证,Singleton的唯一性,属性的唯一行等 违背了Singleton 的初衷


打印机例子
系统中可能存在多台打印设备,但是打印机管理只能有唯一一个, 负责统一调度作业到这些设备,以免一台设备同时接到多个任务引发故障.



单例实现 1

这种方式是最早的一种实现方式, 也称为懒模式(lazy) 只用在被引用是,才会被初始化.

class
  Singleton  {
public :
    static   Singleton Instance () {
            if
 (! instance
                instance  =  new  Singleton();

            return *instance;

    }

private:

    Singleton(){ }

private:

    static Singletoninstance;

};

// implementation

Singleton Singleton::instance = 0;

此方式在多线程环境下存在缺陷, 做如下修改,貌似解决了此问题


class Singleton {

public:

    static Singleton& Instance() {

         if (!instance) {

          //multi-thread lock here

          instance = new Singleton();

         }


         return *instance;

    }

private:

    Singleton(){}

private:

    static Singleton* instance;

};


问题: 在注释的位置加锁,看似解决了问题, 但是,实际上, 当初次引用便有两个请求同时到达的时候, 锁便形同虚设,没起到作用,反而导致内存泄漏,数据错误。

那么如何修改此问题呢? 稍作修改,使用双重检测锁定(Double-checked Locking) 模式,便可以解决此问题:

class Singleton {

public:

    static SingletonInstance() {

         if (!instance) {

              // multi-thread lock here

              if (!instance) {

                        instancenew Singleton();

              }

         }


         return *instance;

    }


private:

    Singleton(){}

private:

    static Singleton*instance;

};



单例实现 2
上面的实现中,已经完全实现了 Singleton,构造函数是私有, 客户必须通过唯一一个访问点(Instance() )来访问 Singleton,并且创建。 在编译时刻已经决定了 单例的唯一性。这也正是 C++ 实现 Singleton 的精髓所在。

Singleton会在“第一次被需求时才会诞生”, 优点无可挑剔, 但是问题也随之而来, 如何销毁 它呢? 如果说内存泄漏, 那基本上不会, 因为现代操作系统都会将进程的内存随着进程的消亡而回收。 但是如果这个 单例是一个数据库 或者 TCP/IP 连接呢? 或者是文件句柄、操作系统互斥信号? 等等就会造成资源泄漏了,会带来意想不到的灾难。

以前见过一些代码里面,也确实有个在我旁边的同事写出过这样的代码来解决这个问题:

class Singleton {

public:

    static SingletonInstance() {

         return instance;

    }

private:

    Singleton(){}

private:

    static Singleton instance;

};

Singleton Singleton::instance;

这样, Singleton 会依赖我们的系统而销毁, 达到了目的。 这样就没问题了吗? 在很多C++大型项目中, 系统会被分解为模块, 模块又会被分解为各个编译单元,即源文件;下面的代码会发生什么事情?

static Object global = Singleton::Instance().DoSomething();

Global 和 Singleton::instance 都是静态全局的变量, C/C++没有规定初始化的顺序, 所以我们只能祈求上帝了!




单例实现 3

上面 两种方式都实现了单例,并且解决了多线程问题,各有优点,很多时候, 有没有更好的办法来替换呢? 下面介绍的这种方式被称为 优雅的单例,既没有动态内存分配,也没有全局的对象产生, 而是巧妙的利用编译器提供的静态数据处理,实现了 懒模式 的单例,而且避免了多线程问题, 创建问题也得以解决,而且在销毁的时候也保证了顺序(根据C++标准规定的变量初始化和销毁的LIFO规则, 后创建的对象先销毁, C标准库 atexit() 会严格保证顺序的正确性)。


class Singleton {

public:

    static Singleton&Instance() {

         static Singleton instance;

         return instance;

    }

private:

    Singleton(){}

};


如果你问我,为何两种实现都要返回 引用(Reference) 而不是指针(Pointer) 呢?

我说,大师说了,能用引用,尽量用。

你再问, 你这是滥用, 这是C++,不是Java!

那我只能说,你喜欢返回指针也没关系,修改一个符号满足你的个性需求吧! 我只追求 程序优雅完美,不倾向那种特定的语言,只要是好的东西,我们就应该吸收它!



OK,先介绍两种最简单的方式, 关于更多 单例(Singleton)以及设计模式的问题,下面会接着讨论....

...



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值