设计模式之单例模式

来源:互联网 发布:如何应对压力 知乎 编辑:程序博客网 时间:2024/06/04 19:17

什么是设计模式

Patterns,顾名思义,具有某种重复性规律的方案。Design Patterns,就是设计过程中可以反复使用的、可以解决特定问题的设计方法。
他并不是某种特定的方法,而是一种解决问题的思维。

设计模式六大原则

1. 开闭原则
意思:软件模块应该对扩展开放,对修改关闭。
举例:在程序需要进行新增功能的时候,不能去修改原有的代码,而是新增代码,实现一个热插拔的效果(热插拔:灵活的去除或添加功能,不影响到原有的功能)。
目的:为了使程序的扩展性好,易于维护和升级
2. 里氏代换原则
意思:里氏代换原则是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
举例:球类,原本是一种体育用品,它的衍生类有篮球、足球、排球、羽毛球等等,如果衍生类替换了基类的原本方法,如把体育用品改成了食用品(那么软件单位的功能受到影响),就不符合里氏代换原则。
目的:对实现抽象化的具体步骤的规范。
3. 依赖倒转原则
意思:针对接口编程,而不是针对实现编程。
举例:以计算机系统为例,无论主板、CPU、内存、硬件都是在针对接口设计的,如果针对实现来设计,内存就要对应到针对某个品牌的主板,那么会出现换内存需要把主板也换掉的尴尬。
目的:降低模块间的耦合。
4. 接口隔离原则
意思:使用多个隔离的接口,比使用单个接口要好。
举例:比如:登录,注册时属于用户模块的两个接口,比写成一个接口好。
目的:提高程序设计灵活性。
5. 迪米特法则(最少知道原则)
意思:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
举例:一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。
目的:降低类之间的耦合,减少对其他类的依赖。
6. 单一职责原则
意思:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
举例:该原则意思简单到不需要举例!
目的:类的复杂性降低,可读性提高,可维护性提高。

高内聚,低耦合

内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系;
耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。

何为单例模式

单例模式也称为单件模式、单子模式,可能是使用最广泛的设计模式。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
实现要点:
1.单例类保证全局只有唯一一个自行创建的实例对象;
2.单例类提供获取这个唯一实例的接口。

优缺点:
优点:
1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
3.提供了对唯一实例的受控访问。
4.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
5.允许可变数目的实例。
6.避免对共享资源的多重占用。
缺点:
1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
3.单例类的职责过重,在一定程度上违背了“单一职责原则”。
4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

懒汉模式:
简单的单例模式实现一:构造函数声明为private或protect防止被外部函数实例化,内部保存一个private static的类指针保存唯一的实例,实例的动作由一个public的类方法代劳,该方法也返回单例类唯一的实例。

#include <iostream>using namespace std;class Singleton{public:    static Singleton* GetInstance()//获取唯一对象实例的接口函数    {        if (NULL == _inst)        {            _inst = new Singleton;        }        return _inst;//返回实例化的唯一对象    }    void Print()    {        cout << "Singleton:" <<_inst<< endl;    }private:    Singleton()    {}    Singleton(const Singleton&);    Singleton& operator=(const Singleton&);    static Singleton* _inst;//指向实例的指针定义为静态私有,这样定义静态成员函数获取对象实例 };Singleton* Singleton::_inst = NULL;//给静态成员指针初始化void TestLazy(){    Singleton::GetInstance()->Print();    Singleton::GetInstance()->Print();    Singleton::GetInstance()->Print();    Singleton::GetInstance()->Print();}int main(){    TestLazy();    return 0;}

实现方式二:考虑线程安全的写法
上述实现代码虽然大体功能已经符合要求,但是考虑细节问题时,它还是有很多纰漏的。诸如:在获取对象时的判断语句NULL == _inst, 如果内存中有两个线程同时在执行这句代码,在看到_inst时,他们都认为指针为空,于是同时进入if语句创建实例,从而违背单例模式的要求。

#include<iostream>using namespace std;#include<mutex>class Singleton{public:    static Singleton* GetInstance()    {        _mtx.lock();//加锁,加锁期间其余线程不能访问该临界区        if (NULL == _inst)        {            _inst = new Singleton;        }        _mtx.unlock();//解锁        return _inst;    }    void Print()    {        cout << "Singleton:" <<_inst<< endl;    }private:    Singleton()    {}    Singleton(const Singleton&);    Singleton& operator=(const Singleton&);    static Singleton* _inst;// 指向实例的指针定义为静态私有,这样定义静态成员函数获取对象实例     int _a;     static mutex _mtx; // 保证线程安全的互斥锁};Singleton* Singleton::_inst = NULL;//给静态指针赋值mutex Singleton::_mtx;//_mtx会调用mutex默认的无参构造函数,所以不用初始化void TestLazy(){    Singleton::GetInstance()->Print();    Singleton::GetInstance()->Print();    Singleton::GetInstance()->Print();    Singleton::GetInstance()->Print();}

饿汉模式:
饿汉模式本来就是线程安全的。

#include<iostream>using namespace std;#include<cassert>class Singleton    {    public:        static Singleton& GetInstance()        {            assert(_inst);            return *_inst;        }        void Print()        {            cout << "Singleton:" << _a << endl;        }    protected:        Singleton()            :_a(0)        {}        Singleton(const Singleton&);        Singleton& operator=(const Singleton&);        static Singleton* _inst;        int _a;    };    Singleton* Singleton::_inst = new Singleton;//静态成员在main函数之前初始化    void Test()    {        Singleton::GetInstance().GetInstance().Print();        Singleton::GetInstance().GetInstance().Print();        Singleton::GetInstance().GetInstance().Print();    }

懒汉与饿汉:
单例大约有两种实现方法:懒汉与饿汉。
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,所以上边的经典方法被归为懒汉实现;
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。

特点与选择:
由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
在访问量较小时,采用懒汉实现。这是以时间换空间。

原创粉丝点击