设计模式
1 类与类之间的关系
- 继承(泛化)
- 组合:整体对象可以控制成员对象的生命周期,一旦主体对象不存在,成员对象也不存在,整体对象和成员对象之间具有同生共死的关系,例如人的头部与眼、耳朵
- 聚合:成员对象是整体的一部分,但是成员对象可以脱离主体对象独立存在。主体对象析构到的时候成员对象依旧可以存在,例如森林和动物、植物,没有森林的地方依旧可以有动植物
- 关联:通常是一个类对象,作为另一个类的成员。有单向关联、双向关联、自关联三种
- 依赖:大多数情况下依赖关系体现在某个类的方法使用另一个类的对象作为参数。
联和聚合的区别主要在于语义上:关联的两个对象之间一般是平等的,聚合和组合则一般是不平等的。
上述类之间的关系强弱由上到下依次减弱
2 设计模式三原则
首先明确类是什么:一组相关属性和行为的集合
单一职责原则
类的功能尽量单一,不要试图让一个类做很多事情,否则其功能发生变化时会变得难以维护
开放封闭原则(多态)
可以扩展,但是不可以修改。通常是在旧类中预留一个接口(虚函数或纯虚函数),在子类中进行重写,其实就是多态,当这个类遇到自己无法解决的问题时,可以找其它类来帮忙。
依赖倒转原则(多态)
- 高层模块不应该依赖低层模块,两个都应该依赖抽象。
- 抽象不应该依赖细节,细节应该依赖抽象。
对于第一条可以举个例子,我们的程序中大量使用了某个API,但是在后续开发中发现用另一种API更合适,但是这个时候程序是很难维护的。这里面高层模块可以认为是程序,低层模块是我们调用的API,如果在两层之间加入一个抽象,通过虚函数来实现具体使用哪一个API就会易于维护
对于第二条可以用里氏代换原则来判断,如果不满足里氏代换原则就不满足依赖倒转原则的第二条。里氏代换原则是指:子类如果想要替代父类对象,父类的规则(属性)必须同样适用于子类对象
2 单例模式
如果在一个类中存在大量全局变量,这里用一下那里也用一下,会让程序看起来很糟糕,比较合适的做法是提供一个唯一的接口来访问。
在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就叫单例模式。
如果使用单例模式,首先要保证这个类的实例对象有且仅有一个,那么就需要采取一定的手段来限制类创建对象,涉及一个类多对象操作的函数有以下几个:
- 构造函数:创建一个新的对象
- 拷贝构造函数:根据已有对象拷贝出一个新的对象
- 拷贝赋值操作符重载函数:两个对象之间的赋值
可进行的处理如下:
1.构造函数私有化,在类内部只调用一次,这个是可控的。
2.由于使用者在类外部不能使用构造函数,所以在类内部创建的这个唯一的对象必须是静态的,这样就可以通过类名来访问了,为了不破坏类的封装,我们都会把这个静态对象的访问权限设置为私有的。
- 在类中只有它的静态成员函数才能访问其静态成员变量,所以可以给这个单例类提供一个静态函数用于得到这个静态的单例对象。
- 拷贝构造函数私有化或者禁用(使用 = delete)
3.拷贝赋值操作符重载函数私有化或者禁用(从单例的语义上讲这个函数已经毫无意义,所以在类中不再提供这样一个函数,故将它也一并处理一下。)
// 定义一个单例模式的类
class Singleton
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
Singleton(const Singleton& obj) = delete;
Singleton& operator=(const Singleton& obj) = delete;
static Singleton* getInstance();
private:
Singleton() = default;
static Singleton* m_obj;
};
//初始化
Singleton *Singleton::m_obj = new Singleton;//内部成员可以调用私有函数
饿汉模式与懒汉模式
单例模式的具体实现有两种:懒汉模式和饿汉模式,在上述的实现中,就是典型的饿汉模式。
饿汉模式就是在程序启动时就被创建并初始化,这样就得到了一个唯一的可用对象,饿汉模式可以确保没有线程安全问题,多线程可以同时访问。
这里说的多线程安全是因为它的创建和初始化在程序启动阶段就完成了,因此不需要在线程间进行并发的初始化操作,保证了单例模式的唯一性,如果涉及到对实例对象数据修改的问题,那么就要另外采取措施了
懒汉模式是在类加载的时候不去创建这个唯一的实例,而是在需要使用的时候再进行实例化。懒汉模式是有线程安全问题的,多个线程可能会同时检测到单例对象没有创造出来,同时去创建就造成了多个实例对象同时存在,违背了单例的原则
class TaskQueue
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance()
{
if(m_taskQ == nullptr)
{
m_taskQ = new TaskQueue;
}
return m_taskQ;
}
private:
TaskQueue() = default;
static TaskQueue* m_taskQ;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;
4 工厂模式
简单工厂模式
只有一个工厂类和工厂函数,工厂函数根据指定类生产不同的对象。它的缺点是不符合开闭原则,因为想让工厂生产新的对象时,需要修改已有的工厂类中的工厂函数
工厂模式
要生产n个对象就需要n个工厂,每个工厂只负责生产一种对象。主要是定义一个工抽象类,再建立多个子工厂,通过多态的方式使得可以动态指定哪个工厂来生产
抽象工厂模式
对于每个对象的不同属性再指定抽象类来实现