什么是设计模式
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();
}
懒汉与饿汉:
单例大约有两种实现方法:懒汉与饿汉。
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,所以上边的经典方法被归为懒汉实现;
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。
特点与选择:
由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
在访问量较小时,采用懒汉实现。这是以时间换空间。