软件开发常用设计模式—单例模式总结

单例模式:就是只有一个实例。

singleton pattern单例模式:确保某一个类在程序运行中只能生成一个实例,并提供一个访问它的全局访问点。这个类称为单例类。如一个工程中,数据库访问对象只有一个,电脑的鼠标只能连接一个,操作系统只能有一个窗口管理器等,这时可以考虑使用单例模式。

众所周知,c++中,类对象被创建时,编译系统为对象分配内存空间,并自动调用构造函数,由构造函数完成成员的初始化工作,也就是说使用构造函数来初始化对象。

1、那么我们需要把构造函数设置为私有的 private,这样可以禁止别人使用构造函数创建其他的实例。

2、又单例类要一直向系统提供这个实例,那么,需要声明它为静态的实例成员,在需要的时候,才创建该实例。

3、且应该把这个静态成员设置为 null,在一个public 的方法里去判断,只有在静态实例成员为 null,也就是没有被初始化的时候,才去初始化它,且只被初始化一次。

通常我们可以让一个全局变量使得一个对象被访问,但它不能阻止你实例化多个对象。如果采用全局或者静态变量的方式,会影响封装性,难以保证别的代码不会对全局变量造成影响。

一个最好的办法是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法,单例模式比全局对象好还包括,单例类可以继承。

单例模式又分为两种基本的情形:饿汉式和懒汉式

直接在静态区初始化 instance,然后通过 get 方法返回,这样这个类每次直接先生成一个对象,好像好久没吃饭的饿汉子,急着吃饭一样,急切的 new 对象,这叫做饿汉式单例类。或者是在 get 方法中才 new instance,然后返回这个对象,和懒汉字一样,不主动做事,需要调用 get 方法的时候,才 new 对象,这就叫做懒汉式单例类。

如下是懒汉式单例类

 1 //单例模式示例
 2 class Singleton
 3 {
 4 public:
 5     static Singleton * getInstance()
 6     {
 7         if (instance == NULL) {
 8             instance = new Singleton();
 9         }
10
11         return instance;
12     }
13
14 private:
15     //私有的构造函数,防止外人私自调用
16     Singleton()
17     {
18         cout << "实例化了" << count << "个对象!" << endl;
19         count++;
20     }
21     //声明一个静态实例,静态函数只能使用静态的数据成员。整个类中静态成员只有一个实例,通常在实现源文件中被初始化。
22     static Singleton *instance;
23     //记录实例化的对象
24     int count = 1;
25 };
26
27 Singleton * Singleton::instance = NULL;
28
29 int main(void)
30 {
31     Singleton::getInstance();
32     Singleton::getInstance();
33     Singleton::getInstance();
34     Singleton::getInstance();
35
36     return 0;
37 }

实例化了1个对象!

Program ended with exit code: 0

小结:

懒汉式单例模式是用时间换取控件,饿汉式单例模式,是用空间换取时间。

继续分析,考虑多线程下的懒汉式单例模式

上述代码在单线程的情况下,运行正常,但是遇到了多线程就出问题,假设有两个线程同时运行了这个单例类,同时运行到了判断 if 语句,并且当时,instance 实例确实没有被初始化呢,那么两个线程都会去运行并创建实例,此时就不满足单例类的要求了。那么我们需要写上线程同步的功能。

 1 //考虑到多线程情形下的单例模式
 2 class Singleton
 3 {
 4 public:
 5     //get 方法
 6     static Singleton * getInstance(){
 7         //联系互斥信号量机制,给代码加锁
 8         lock();
 9         //判断 null
10         if (NULL == instance) {
11             //判断类没有生成对象,才实例化对象,否则不再实例化
12             instance = new Singleton();
13         }
14         //使用完毕,解锁
15         unlock();
16         //返回一个实例化的对象
17         return instance;
18     }
19 private:
20     //声明对象计数器
21     int count = 0;
22     //声明一个静态的实例
23     static Singleton *instance;
24     //私有构造函数
25     Singleton(){
26         count++;
27         cout << "实例化了" << count << "个对象!" << endl;
28     }
29 };
30 //初始化 instance
31 Singleton * Singleton::instance = NULL;

此时,还是有 ab 两个线程来运行这个单例类,由于在同一时刻,只有一个线程能拿到同步锁(互斥信号量机制),a 拿到了同步锁,b 只能等待,如果 a发现实例还没创建,a 就会创建一个实例,创建完毕,a 释放同步锁,然后 b 才能拿到同步锁,继续运行接下来的代码,b 发现 a 线程运行的时候,已经生成了一个实例,b 线程就不会重复创建实例了,这样就保证了我们在多线程环境中只能得到一个实例。

继续分析多线程下的懒汉式单例模式

代码中,每次 get 方法中,得到 instance,都要判断是否为空,且判断是否为空之前,都要先加同步锁,如果线程很多的时候,就要先等待加了同步锁的线程运行完毕,才能继续判断余下的线程,这样就会造成大量线程的阻塞,且加锁是个非常消耗时间的过程,应该尽量避免(除非很有必要的时候)。可行的办法是,双重判断方法。

因为,只是在实例还没有创建的时候,需要加锁判断,保证每次只有一个线程创建实例,而当实例已经创建之后,其实就不需要加锁操作了。

双重判断的线程安全的懒汉式单例模式

 1 class Singleton
 2 {
 3 public:
 4     //get 方法
 5     static Singleton * getInstance(){
 6         //先判断一次 null,只有 null 的时候需要加锁,其他的时候,其实不需要加锁
 7         if (NULL == instance) {
 8             //联系互斥信号量机制,给代码加锁
 9             lock();
10             //然后再次判断 null
11             if (NULL == instance) {
12                 //判断类没有生成对象,才实例化对象,否则不再实例化
13                 instance = new Singleton();
14             }
15             //使用完毕,解锁
16             unlock();
17         }
18                 //返回一个实例化的对象
19         return instance;
20     }
21 private:
22     //声明对象计数器
23     int count = 0;
24     //声明一个静态的实例
25     static Singleton *instance;
26     //私有构造函数
27     Singleton(){
28         count++;
29         cout << "实例化了" << count << "个对象!" << endl;
30     }
31 };
32 //初始化 instance
33 Singleton * Singleton::instance = NULL;

这样的双重检测机制,提高了单例模式在多线程下的效率,因为这样的代码,只需要在第一次创建实例的时候,需要加锁,其他的时候,线程无需排队等待加锁之后,再去判断了,比较高效。

再看饿汉式的单例模式,之前看了懒汉式的单例类,是线程不安全的,通过加锁(双重锁),实现线程安全

回忆饿汉式单例类:直接在静态区初始化 instance,然后通过 get 方法返回,这样这个类每次直接先生成一个对象,好像好久没吃饭的饿汉子,急着吃饭一样,急切的 new 对象,这叫做饿汉式单例类。

 1 class Singleton
 2 {
 3 public:
 4     //get 方法
 5     static Singleton * getInstance(){
 6         //返回一个实例化的对象
 7         return instance;
 8     }
 9 private:
10     //声明一个静态的实例
11     static Singleton *instance;
12     //私有构造函数
13     Singleton(){
14
15     }
16 };
17 //每次先直接实例化instance,get 方法直接返回这个实例
18 Singleton * Singleton::instance = new Singleton();

注意:静态初始化实例可以保证线程安全,因为静态实例初始化在程序开始时进入主函数之前,就由主线程以单线程方式完成了初始化!饿汉式的单例类,也就是静态初始化实例保证其线程安全性,故在性能需求较高时,应使用这种模式,避免频繁的锁争夺。

继续看单例模式

上面的单例模式没有 destory() 方法,也就是说,貌似上面的单例类没有主动析构这个唯一实例!然而这就导致了一个问题,在程序结束之后,该单例对象没有delete,导致内存泄露!下面是一些大神的方法:一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行。

我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。如果在类的析构行为中有必须的操作,比如关闭文件,释放外部资源,那么上面的代码无法实现这个要求。我们需要一种方法,正常的删除该实例。利用这些特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的Garbage类:

 1 class Singleton
 2 {
 3 public:
 4     //get 方法
 5     static Singleton * getInstance(){
 6         //判断单例否
 7         if (NULL == instance) {
 8             instance = new Singleton();
 9         }
10         //返回一个实例化的对象
11         return instance;
12     }
13     //c++ 嵌套的内部类,作用是删除单例类对象,Garbage被定义为Singleton的内嵌类,以防该类被在其他地方滥用。
14     class Garbage
15     {
16     public:
17         ~Garbage(){
18             if (Singleton::instance != NULL) {
19                 cout << "单例类的唯一实例被析构了" << endl;
20                 delete Singleton::instance;
21             }
22         }
23     };
24
25 private:
26     //单例类中声明一个触发垃圾回收类的静态成员变量,它的唯一工作就是在析构函数中删除单例类的实例,利用程序在结束时析构全局变量的特性,选择最终的释放时机;
27     static Garbage garbage;
28     //声明一个静态的实例
29     static Singleton *instance;
30     //单例类的私有构造函数
31     Singleton(){
32         cout << "调用了单例类的构造函数" << endl;
33     }
34     //单例类的私有析构函数
35     ~Singleton(){
36         cout << "调用了单例类的析构函数" << endl;
37     }
38 };
39 //初始化内部的静态变量,目睹是启动删除的析构函数,如果不初始化,就不会被析构
40 //内部类可以访问外部类的私有成员,外部类不能访问内部类的私有成员!
41 Singleton::Garbage Singleton::garbage;
42 //初始化instance为 null
43 Singleton * Singleton::instance = NULL;
44
45 int main(void)
46 {
47     Singleton *a = Singleton::getInstance();
48     Singleton *b = Singleton::getInstance();
49     Singleton *c = Singleton::getInstance();
50
51     if (a == b) {
52         cout << "a = b" << endl;
53     }
54
55     return 0;
56 }

调用了单例类的构造函数

a = b

单例类的唯一实例被析构了

调用了单例类的析构函数

Program ended with exit code: 0

类Garbage被定义为Singleton的内嵌类,以防该类在其他地方滥用,程序运行结束时,系统会调用Singleton的静态成员garbage的析构函数,该析构函数会删除单例的唯一实例,使用这种方法释放单例对象有以下特征:

1、在单例类内部定义专有的嵌套类;

2、在单例类内定义私有的专门用于释放的静态成员;

3、利用程序在结束时析构全局变量的特性,选择最终的释放时机;

4、使用单例的代码不需要任何操作,不必关心对象的释放。

其实,继续想单例类的实现,有的人会这样做:

在程序结束时调一个专门的方法,这个方法里判断实例对象是否为 null,如果不为 null,就对返回的指针掉用delete操作。这样做可以实现删除单例的功能,但不仅很丑陋,而且容易出错。因为这样的附加代码很容易被忘记,而且也很难保证在delete之后,没有代码再调用GetInstance函数。不推荐直接的删除方法。

继续查看单例模式:单例模式在实际开发过程中是很有用的,单例模式的特征总结:

1、一个类只有一个实例

2、提供一个全局访问点

3、禁止拷贝

逐个分析:

1、实现只有一个实例,需要做的事情:将构造函数声明为私有

2、提供一个全局访问点,需要做的事情:类中创建静态成员和静态成员方法

3、禁止拷贝:把拷贝构造函数声明为私有,并且不提供实现,将赋值运算符声明为私有,防止对象的赋值

完整的单例类实现代码如下:

class Singleton
{
public:
    //get 方法
    static Singleton * getInstance(){
        if (NULL == instance) {
            lock();
            //判断单例否
            if (NULL == instance) {
                instance = new Singleton();
            }
            unlock();
        }
        //返回一个实例化的对象
        return instance;
    }
    //c++ 嵌套的内部类,作用是删除单例类对象,Garbage被定义为Singleton的私有内嵌类,以防该类被在其他地方滥用。
    class Garbage
    {
    public:
        ~Garbage(){
            if (Singleton::instance != NULL) {
                cout << "单例类的唯一实例被析构了" << endl;
                delete Singleton::instance;
            }
        }
    };

private:
    //单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例,利用程序在结束时析构全局变量的特性,选择最终的释放时机;
    static Garbage garbage;
    //声明一个静态的实例
    static Singleton *instance;
    //单例类的私有构造函数
    Singleton(){
        cout << "调用了单例类的构造函数" << endl;
    }
    //单例类的私有析构函数
    ~Singleton(){
        cout << "调用了单例类的析构函数" << endl;
    }
    //把拷贝构造函数声明为私有,就可以禁止外人拷贝对象,也不用实现它,声明私有即可
    Singleton(const Singleton &copy);
    //把赋值运算符重载为私有的,防止对象之间的赋值操作
    Singleton & operator=(const Singleton &other);
};
//初始化内部似有泪的静态变量,目睹是启动删除的析构函数,如果不初始化,就不会被析构
//内部类可以访问外部类的私有成员,外部类不能访问内部类的私有成员!
Singleton::Garbage Singleton::garbage;
//初始化instance为 null
Singleton * Singleton::instance = NULL;

int main(void)
{
    Singleton *a = Singleton::getInstance();
    Singleton *b = Singleton::getInstance();
    Singleton *c = Singleton::getInstance();

    if (a == b) {
        cout << "a = b" << endl;
    }

    return 0;
}

单例类de测试,两个方法:

1、实例化多个对象,看调用了几次构造函数,如果只调用一次,说明只创建一个实例

2、单步跟踪,查看对象的地址,是否一样,一样则为一个对象

时间: 2024-12-25 03:29:26

软件开发常用设计模式—单例模式总结的相关文章

软件开发常用设计模式—iOS 中的代理模式总结

比如现在有一个人,想要买一张电影票,但是她很忙碌,没时间去买,那怎么办呢?只能说委托给另一个人去买. 此时,需要 person 给 other 发送消息,通知 other 去给她买电影票,而 other 也要反馈消息给 person,此时 other 就是一个代理人,person 委托代理人去办事情(买票).代理人是给委托人代办一些事情的人.具体代理人怎么做的这些事情,委托人不管,委托人只看反馈. 先看代理设计模式的基本原理 #import <Foundation/Foundation.h>

软件开发常用设计模式—门面模式总结

facade 门面模式定义:有的人也叫它外观模式 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这个子系统更加容易使用. 模式说明: 子系统(SubSystem):负责处理复杂的逻辑处理.不直接跟客户打交道.门面(Facade):负责跟子系统进行交互,提供简单易用的功能或接口给客户.客户(Client):通过门面和子系统进行交互,不需关心子系统的细节. 主要作用: 简化复杂接口, 解耦和来屏蔽客户端对子系统的直接访问 c++实现代码(网上大牛提供) 1 #incl

IOS开发常用设计模式

IOS开发常用设计模式 说起设计模式,感觉自己把握不了笔头,所以单拿出iOS开发中的几种常用设计模式谈一下. 单例模式(Singleton) 概念:整个应用或系统只能有该类的一个实例 在iOS开发我们经常碰到只需要某类一个实例的情况,最常见的莫过于对硬件参数的访问类,比如UIAccelerometer.这个类可以帮助我们获得硬件在各个方向轴上的加速度,但是我们仅仅需要它的一个实例就够了,再多,只会浪费内存. 所以苹果提供了一个UIAccelerometer的实例化方法+sharedAcceler

IOS常用设计模式——单例模式(IOS开发)

IOS常用的设计模式包括:单例模式.委托模式.观察者模式和MVC模式. 这里主要讲单例模式 单例模式 -问题: 主要解决应用中只有一个实例的问题(只需要某个类的实例) -原理:一般会封装一个静态属性,并提供静态实例的创建方法 -应用:单例类 // Singleton.h @interface Singleton : NSObject + (Singleton *)sharedManager; @property (nonatomic, strong) NSString* stingletonDa

iOS常用设计模式——单例模式

第一部分: 创建一个单例对象 单例的应用场景: 单例模式用于当一个类只能有一个实例的时候, 通常情况下这个"单例"代表的是某一个物理设备比如打印机,或是某种不可以有多个实例同时存在的虚拟资源或是系统属性比如一个程序的某个引擎或是数据.用单例模式加以控制是非常有必要的. 什么是单例模式? 单例是一种常用的软件设计模式.在应用这个模式时,单例对象的类必须保证只有一个实例存在.许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为. 单例设计模式需要达到下面几个目的:1.

IT软件开发常用英语词汇

A abstract 抽象的 abstract base class 抽象基类 abstract class 抽象类 abstraction 抽象.抽象物.抽象性 access 存取.访问 access function 访问函数 access level 访问级别 account 账户 action 动作 activate 激活 actual parameter 实参 adapter 适配器 add-in 插件 address 地址 address space 地址空间 ADO(ActiveX

【转】IT软件开发常用英语词汇

来源自网络 目录 A B C D E F G H I J K L M N O P Q R S T U V W X   A abstract 抽象的 abstract base class 抽象基类 abstract class 抽象类 abstraction 抽象.抽象物.抽象性 access 存取.访问 access function 访问函数 access level 访问级别 account 账户 action 动作 activate 激活 actual parameter 实参 adap

工作笔记2.软件开发常用工具

上文中我们介绍<工作总结1.如何高效跟客户确定需求?> 需求确定以后,下一步就是:做一份项目计划,着手开发了!本文将简介项目开发过程中的常用软件 本文中软件介绍的先后顺序,是和实际开发相一致的: 一.用*工具?开发*图? 1.Microsoft Project控制项目进度 可以打开.打印和导出MicrosoftProject文件.该软件可以显示使用Microsoft Project创建的项目并且可以以MPP.XML或者数据库格式进行存储.该软件让你可以按照Gantt Diagram.资源表格.

软件开发常用的linux命令心得(ubuntu为例)

软件开发过程中难免要经常对主机进行配置或者部署等操作,想到一些就写一些了,以后再更新 解压命令: a.如果是tar文件,则直接用 “tar zxvf 文件名”: b.如果是zip文件,用 “unzip 文件名”: 下载和安装卸载: a.下载文件或脚本,"wget url": b.安装软件, "apt-get install software": c.卸载软件,卸载软件比较麻烦 "apt-get purge software",这样删除之后可能会有