设计模式简介
设计模式实际上是为了提高代码的可用性以及更好的维护。不能为了设计模式而写设计模式,这样反而会适得其反。设计模式推荐的书籍:GOF设计模式,这本是设计模式的经典中的经典,非常值得一读;还有一本研磨设计模式,也写的很好。
观察者模式
观察者模式是代码中经常使用的一种设计模式,主要的有点是松耦合。观察者模式主要有两种模型:推模型和拉模型。推模型指的是将通知一次性广播出去,拉模型是指需要对端来订阅,订阅之后会收到相应的消息,粒度较小。
我们可以先通过一个简单的图来大概了解一下观察者模式。比如说我们的一个报纸订阅的过程。
subject是主题,observer如果需要得到消息的话就需要就加入list,然后通知的时候遍历list,在这个list中的对象都能获取到这个对象的消息。
见如下代码:
#include <iostream>
#include <list>
using namespace std;
class Subject;
class Observer
{
public:
Observer(){}
virtual ~Observer(){}
virtual void update(Subject *subject) = 0;
virtual void update(string content) = 0;
};
class Subject
{
public:
Subject() {}
virtual ~ Subject() {}
virtual string getContent() = 0;
virtual string getAbstractContent() = 0;
virtual void setContent(string content) = 0;
// 订阅主题
void attach(Observer *observer) {
observers.push_back(observer);
}
// 取消订阅
void detach(Observer *observer) {
observers.remove(observer);
}
// 通知所有的订阅者
virtual void notifyObservers() {
for(Observer *reader: observers) {
// 拉模型 (也是有推送的性质,只是粒度小一些)
reader->update(this);
}
}
// 通知所有的订阅者
virtual void notifyObservers(string content) {
for(Observer *reader: observers) {
// 推模型
reader->update(content);
}
}
private:
list<Observer *> observers; // 保存注册的观察者
};
class Reader : public Observer
{
public:
Reader() {}
virtual ~Reader() {}
virtual void update(Subject *subject) {
// 调用对应的方法去拉取内容 subject->getContent()
cout << m_name << "收到报纸和阅读它, 具体内容" << subject->getContent() << endl;
}
virtual void update(string content) {
// 推模型
cout << m_name << "收到报纸和阅读它, 具体内容" << content << endl;
}
string getName() {
return m_name;
}
void setName(string name) {
m_name = name;
}
private:
string m_name;
};
class NewsPaper: public Subject
{
public:
NewsPaper() {}
virtual ~NewsPaper() {}
void setContent(string content) {
m_content = content;
notifyObservers();
}
virtual string getContent() {
return m_content;
}
virtual string getAbstractContent() {
return "摘要:";
}
private:
string m_content;
};
int main()
{
// 创建一个报纸主题
NewsPaper *subject = new NewsPaper();
// 创建观察者,读者
Reader *reader1 = new Reader();
reader1->setName("aaa");
Reader *reader2 = new Reader();
reader2->setName("bbb");
Reader *reader3 = new Reader();
reader3->setName("ccc");
subject->attach(reader1);
subject->setContent("Hello World!22222");
cout << "-----------------------" << endl;
subject->attach(reader2);
subject->attach(reader3);
subject->setContent("Hello World!11111");
cout << "Hello World!" << endl;
return 0;
}
以上程序可以看出,三个阅读者订阅了报纸,当我们将阅读者1加入之后,阅读者1就会收到通知,后面再将两个加入到list中的话,后面两个也会收到通知,就类似与订阅报纸之后就会收到推送的新闻一样。
上面代码中还有一点是有两种模型,推模型和拉模型。简单的讲,推模型就是只要对象加入到subject的list之后,就会收到通知。拉模型则需要对象满足一定的条件,或者需要注册,这样才能得到相应的消息。一般比较常用的是拉模型。
观察者模式解决的是一种一对多的稳定的依赖关系,以便一个对象发生改变时,其他的对象也能收到通知,但是同时需要保持松耦合。
工厂模式
工厂模式主要是定义一个创建对象的接口,可以让子类来决定使用哪个类进行实例化。主要的目的是做到实例化的延迟,主要还是要解耦。
主要解决的问题就是对象的创建可能随着需求的变化而一直变化,工厂模式实现一种封装的机制避免客户程序和对象创建的紧耦合。
先通过下面这张图片来具体感受以下,工厂模式是怎样的一种结构。
通过图片可以看出,有一个creator基类,派生出ConcreteCreator,Product基类派生出的ConcreteProduct调用creator提供的创建实例的函数。
看完图之后,我们通过代码来具体了解以下工厂模式:
下面的代码描述的是将文件导出成不同的格式。
#include <iostream>
using namespace std;
// 导出的文件类
class ExportFileProduct
{
public:
ExportFileProduct() {}
virtual ~ExportFileProduct() {}
/**
* 导出函数,具体实现交给具体的导出方式
* */
virtual bool Export(string data) = 0;
};
// 保存成文件
class ExportTextProduct: public ExportFileProduct
{
public:
ExportTextProduct() {}
virtual ~ExportTextProduct() { }
// 具体的文件导出函数的实现
virtual bool Export(string data) {
cout << "导出数据:[" << data << "]保存成文本的方式" << endl;
return true;
}
};
class ExportDBProduct: public ExportFileProduct
{
public:
ExportDBProduct() {}
virtual ~ExportDBProduct() {}
// 具体的文件导出函数的实现
virtual bool Export(string data) {
cout << "导出数据:[" << data << "]保存数据库的方式" << endl;
return true;
}
};
class ExportFactory
{
public:
ExportFactory() {}
virtual ~ExportFactory() {}
/**
* @brief Export
* @param type 导出的类型
* @param data 具体的数据
* @return
*/
virtual bool Export(int type, string data) {
ExportFileProduct *product = nullptr;
// 根据传入的type参数返回不同类型的对象
product = factoryMethod(type);
bool ret = false;
if(product) {
ret = product->Export(data);
delete product;
} else {
cout << "没有对应的导出方式";
}
return ret;
}
protected:
virtual ExportFileProduct *factoryMethod(int type) {
ExportFileProduct *product = nullptr;
if(1 == type) {
product = new ExportTextProduct();
} else if(2 == type) {
product = new ExportDBProduct();
}
return product;
}
};
// 加一种新的导出方式:
// (1)修改原来的工厂方法
// (2)继承工厂方法去拓展
class ExportXMLProduct: public ExportFileProduct
{
public:
ExportXMLProduct() {}
virtual ~ExportXMLProduct() { }
virtual bool Export(string data) {
cout << "导出数据:[" << data << "]保存XML的方式" << endl;
return true;
}
};
class ExportPortobufferProduct: public ExportFileProduct
{
public:
ExportPortobufferProduct() {}
virtual ~ExportPortobufferProduct() { }
virtual bool Export(string data) {
cout << "导出数据:[" << data << "]保存Portobuffer的方式" << endl;
return true;
}
};
class ExportFactory2: public ExportFactory
{
public:
ExportFactory2() {}
virtual ~ExportFactory2() {}
protected:
virtual ExportFileProduct *factoryMethod(int type) {
ExportFileProduct *product = nullptr;
if(3 == type) {
product = new ExportXMLProduct();
} else if(4 == type) {
product = new ExportPortobufferProduct();
} else {
product = ExportFactory::factoryMethod(type);
}
return product;
}
};
int main()
{
cout << "ExportFactory" << endl;
ExportFactory *factory = new ExportFactory();
factory->Export(1, "hello world");
factory->Export(2, "hello world");
factory->Export(3, "hello world");
cout << "\nExportFactory2" << endl;
ExportFactory *factory2 = new ExportFactory2();
factory2->Export(1, "hello world");
factory2->Export(2, "hello world");
factory2->Export(3, "hello world");
factory2->Export(4, "hello world");
delete factory;
delete factory2;
return 0;
}
上述代码注释已经基本说明了每个函数的作用。主要就是可以通过ExportFactory创建不同类型的ExportFileProduct,然后可以导出具体类型的数据。
工厂模式用于隔离对象的使用者和具体类型之间的耦合关系。将创建对象的操作延迟到了子类中,增加了扩展性。但是工厂模式的缺点就是要求创建的方法和参数相同。
单例模式
保证整个工程中只有一个该对象的实例,提高了代码的维护效率,同时一个实例也增加了安全性。
如上图,单例模式需要提供一个获取实例的函数使得其他需要使用该类中的资源的对象能够获取到当前的实例。
单例模式常见的有懒汉式和饿汉式两种模式。懒汉模式就是在使用的时候创建一个实例,饿汉模式就是在程序开始运行的时候就创建一个全局实例。
代码大致如下:
// 3、锁住初始化实例语句之后再次检查实例是否被创建
/* 双检查锁,但由于内存读写reorder不安全 因为C++创建对象时,会执行1、分配内存,2 调用构造,3 赋值操作三步操作,
然而现代CPU和编译器高并发下可能会进行乱序重排操作,因而创建对象new CSingleton的第2步可能会晚于第3步进行指令调用,
因而导致出现未定义的的行为。*/
class Singleton {
private:
static Singleton *m_singleton;
static mutex m_mutex;
Singleton() = default;
Singleton(const Singleton& s) = default;
Singleton& operator=(const Singleton& s) = default;
class GarbageCollector {
public:
~GarbageCollector() {
cout << "~GarbageCollector\n";
if (Singleton::m_singleton) {
cout << "free m_singleton\n";
delete Singleton::m_singleton;
Singleton::m_singleton = nullptr;
}
}
};
static GarbageCollector m_gc;
public:
void *getSingletonAddress() {
return m_singleton;
}
static Singleton* getInstance() {
if (Singleton::m_singleton == nullptr){
m_mutex.lock(); // 加锁,保证只有一个线程在访问线程内的代码
if (Singleton::m_singleton == nullptr) { //再次检查
m_singleton = new Singleton(); // 对象的new不是原子操作 1、分配内存,2 调用构造,3 赋值操作,到第3步的时候才是m_singleton非空
// 1、分配内存,2 赋值操作 3 调用构造,到第2步的时候才是m_singleton非空
}
m_mutex.unlock();//解锁
}
return m_singleton;
}
// static Singleton s_singleton; // C++11线程安全, C++11之前不是线程安全 __cxa_guard_acquire 和 __cxa_guard_release
// return &s_singleton;
};
Singleton* Singleton::m_singleton = nullptr;
mutex Singleton::m_mutex;
Singleton::GarbageCollector Singleton::m_gc;
// 饿汉式,在main函数运行前初始化,绝对安全
class Singleton {
private:
// Singleton() = default; // 自动生成默认构造函数
Singleton() {
cout << "Singleton construct\n";
}
Singleton(const Singleton& s) = delete; // 禁用拷贝构造函数
Singleton& operator=(const Singleton& s) = delete; // 禁用拷贝赋值操作符
static Singleton m_singleton;
public:
static Singleton* getInstance(){
return &m_singleton;
}
};
Singleton Singleton::m_singleton;
同时,c++11是保证static是线程安全的,因此可以使用静态的局部变量来做懒汉式的单例。
但是c++11内部也有很多坑,最好是显式的申明以下构造函数。
(2)为什么c++在局部静态变量使用的是递归锁。
递归互斥锁基本上可以被一个线程锁定多次,但不能被另一个线程锁定,除非锁定线程将它们解锁相同的次数。这意味着递归/可重入不会导致死锁,但是我们仍然可以一次访问一个线程。
因此为了防止在递归初始化时,local static变量初始化时发生死锁,使用递归锁来抛出异常。
(3)构造函数对多线程安全的影响
显式的构造函数保证c++11的static是线程安全的,使用隐式构造函数的时候就会可能出现static线程不安全的情况。
编译器的版本也是有关系的。