我们一直所说C++是一个面向对象的语言,也了解它的三大基本特性:封装、继承和多态,但是这些和工程实践中的联系却是有一点距离,下面来通过四个设计模式来描述一下面向对象在工程实践中的应用。
访问者模式
情景举例
现在我们有一个基类A, 然后有多个子类B、C、D继承于A类,现在我们有一个存放A类指针的数组来存放每一个对象,对于每一个对象执行相应的类操作(这里简化直接输出子类的名字)。经过思考,之前的智能指针dynamic_cast
可以胜任这样的工作。进行判断后,可以输出相应的信息,代码如下所示:
for (int i = 0; i < n; i++) {
if (dynamic_cast<B *>(arr[i])) { // 通过虚函数表地址来判断是否是B类(指针的强制转换,要求必须是虚函数)
cout << "this is B class" << endl;
} else if (dynamic_cast<C *>(arr[i])) {
cout << "this is C class" << endl;
} else if (dynamic_cast<D *> (arr[i])) {
cout << "this is D class" << endl;
} else {
cout << "error!" << endl;
}
}
这个代码没有问题,但是在工程中就有问题,因为每出现一个子类就需要有一个 if
判断,这显然是不太好的。另外,当我们创建一个新的子类 E 时,如果忘记加 if 判断,程序可以顺利执行,但是只会在运行期才会发现bug,即:“error
”。 C++的一种思想就是尽量把错误显示在编译期间,所以有了访问者模式
可以很好的解决这个问题。
访问者模式
在基类中A设置一个访问接口,即一个访问者类,将里面的访问函数设置成虚函数,并建立一个子类继承它,在子类里面重写访问函数(不同的对象执行不同的操作),同时在基类A里面设置一个调用接口类的函数,同样设置成虚函数。
class A {
public :
class IVisitor { // 接口类
public :
virtual void visit(A *) = 0; // 设置成纯虚函数
virtual void visit(B *) = 0;
};
virtual void Accept(IVisitor *vis) { // 调用接口的方法,传入接口类对象
vis->visit(this);// 调用相应的对象的方法
}
virtual ~A() {}
};
class B : public A {
public :
void Accept(IVisitor *vis) { // 子类重写调用函数
vis->visit(this);
}
};
class OutputVisitor : public A::IVisitor { // 设置一个专门执行操作的类,继承子访问者类,重写操作visit函数功能
public :
virtual void visit(A *obj) {
cout << "this is father class A" << endl;
}
virtual void visit(B *obj) {
cout << "this is a class B object" << endl;
}
};
使用方式也是很简单,省去了大量的 if else 判断,在增加子类,但是没有重写visitor操作的时候在编译期就会报错。
OutputVisitor vis;// 生活一个访问者对象
for (int i = 0; i < n; i++) {
arr[i]->Accept(&vis);// 直接调用函数即可(虚函数跟着对象走)
// 例如arr[i]是B,B->Accept(&vis)相当于执行的是vis->(*B)
}
单例模式
什么是单例模式?简单来说就是程序中只能够有一个对象。例如http传输中的服务器。那么怎么让程序中构造的对象有且只有一个呢?思考的方向就是不能够随意的创建对象,即不能使用类的一系列构造函数,那么就有办法了,将所有构造函数设置成私有的就可以了!
class HttpServer {
private:
HttpServer() {}
HttpServer(const HttpServer &) = delete;
};
接下来的问题又来了,第一个对象怎么获取呢?这时候就需要类方法的时候了,类方法不是成员方法,他不依赖于对象,即时没有对象也可以调用。
class HttpServer {
public :
static HttpServer *getInstance() {
return instance;
}
private:
static HttpServer *instance;
HttpServer() {}
HttpServer(const HttpServer &) = delete;
~HttpServer() {} // 当多个对象共有一个指针的时候,删除对象就出错了
//所以我们要将析构函数设置为私有的
};
现在我们只剩下初始化HttpServer *instance
就可以了,由两种方法可以初始化它,也对应着两种模式:
饿汉模式:在主函数之前就直接初始化一个对象。
HttpServer *HttpServer::instance = new HttpServer();
懒汉模式:就是在程序第一次用到它的时候才实例化第一个对象。
当instance为空的时候实例化一个对象并返回,不为空的时候直接返回对象的指针。
class HttpServer {
public :
static HttpServer *getInstance() {
if (instance == nullptr) {
instance = new HttpServer();
}
return instance;
}
};
HttpServer *HttpServer::instance = nullptr;
现在就是懒汉模式的基本结构了,因为目前仍存在一些bug,就是因为在多线程的情况下,可能会有多个线程同时判断instance=nullpr,这个时候就需要互斥锁了,如下:
class HttpServer {
public :
// 类函数,来返回一个对象的实例化
static HttpServer *getInstance() {
if (instance == nullptr) {
// 只有是第一次的时候才加锁,提高了效率。
std::unique_lock<std::mutex> lock(m_mutex);
if (instance == nullptr) {
instance = new HttpServer();
}
}
return instance;
}
private:
static HttpServer *instance;
static std::mutex m_mutex;
HttpServer() {}
HttpServer(const HttpServer &) = delete;
~HttpServer() {}
};
HttpServer *HttpServer::instance = nullptr;
std::mutex HttpServer::m_mutex;
工厂模式
工厂:抽象类,接口类
工厂类+产品类
创建对象的时候不需要new 很多, 直接交给工厂处理
由于直接交给工厂类负责,所以直接调用工厂的函数,无需每次判断不同的子类,分别new一个
例如:
用时间种子随机创建子类,需要判断才来new不同的对象,现在只需要调用一个工厂类的封装函数
另外: 工厂专门构造对象,即外部不能随意构造对象,应该将构造和拷贝函数设置成为受保护的
而尽量不使用私有的(子类new一个对象的时候,要调用父类的构造函数)。
#include <iostream>
#include <algorithm>
#include <time.h>
using namespace std;
/* 说明: Car的工厂类
* 当Car的属性比较多的时候,生成对象就很麻烦,要初始化很多属性
* 只有工厂可以创建对象, 构造函数和拷贝函数设置成受保护的(私有的,设置子类的就不能构造)
*/
class ICar{
public:
class IFactory { // 工厂类设置在类里面表示车类的工厂
public:
virtual ICar *create() = 0;
};
virtual void run() = 0; // 纯虚函数
protected:
ICar(){}
~ICar(){}
};
class BenzCar : public ICar { // 子类的构造函数设置为私有的,外部不能再创建对象
BenzCar(){}
public:
class Factory : public ICar::IFactory {
public:
virtual ICar *create() { // 子类自己的专门的工厂
return new BenzCar();
}
};
virtual void run() {
cout << "BenzCar run" << endl;
}
};
class BmwCar : public ICar {
BmwCar(){}
public:
class Factory : public ICar::IFactory {
public:
virtual ICar *create() { // 子类自己的专门的工厂
return new BmwCar();
}
};
virtual void run() {
cout << "BmwCar run" << endl;
}
};
//初始化两个工厂
ICar::IFactory *fac[2] = {new BenzCar::Factory(), new BmwCar::Factory()};
int main() {
srand(time(0));
ICar *cars[10];
for (int i = 0; i < 10; i++) {
cars[i] = fac[(rand() % 2)]->create();
}
for (int i = 0; i < 10; i++) {
cars[i]->run();
}
return 0;
}