C++设计模式之观察者者模式:多线程拷贝文件,能够支持多种拷贝文件进度(QT平台)
一、观察者模式
最近在学习设计模式,学习到了观察者模式。观察者模式的定义说明,我就不赘述了。网上有很多资料。直接把我的代码展示给大家,让大家了解观察者模式的用法。
二、实际应用 拷贝文件是一个很常见功能,拷贝文件时,一般都会有进度条的。假如客户有一个新的需求,比如拷贝完成以后有一个提示音,类似于迅雷下载文件完成以后的提示音。用观察者模式就可以很好解决这个新的需求,上代码。
三、抽象的通知类(class IProgress) 这个抽象类用来通知拷贝文件的进度 头文件:
#ifndef IPROGRESS_H
#define IPROGRESS_H
class IProgress { public:
IProgress();
virtual ~IProgress(){}
virtual void DoProgress(float value) = 0;
#endif
源文件:
#include "iprogress.h"
IProgress::IProgress()
{
}`
这个类定义了一个纯虚函数:virtual void DoProgress(float value) = 0来处理拷贝文件进度。 四、拷贝文件类(class CopyFile) 头文件:
#ifndef COPYFILE_H
#define COPYFILE_H
#include <list>
class IProgress;
class CopyFile
{
public:
CopyFile();
~CopyFile();
void addNotice(IProgress *notice);
void removeNotice(IProgress *notice);
void doCopy(const char *srcFileName, const char *descFileName);
protected:
void onProgress(float value);
private:
std::list<IProgress*> m_listIProgress;
};
#endif
源文件:
#include "copyfile.h"
#include "iprogress.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
CopyFile::CopyFile()
{
}
CopyFile::~CopyFile()
{
}
void CopyFile::addNotice(IProgress *notice)
{
m_listIProgress.push_back(notice);
}
void CopyFile::removeNotice(IProgress *notice)
{
m_listIProgress.remove(notice);
}
void CopyFile::doCopy(const char *srcFileName, const char *descFileName)
{
FILE *fp1,*fp2;
char res[1024] = {0};
long long len;
fp1 = fopen(srcFileName,"rb");
fp2 = fopen(descFileName,"wb");
fseek(fp1,0,SEEK_END);
len = _ftelli64(fp1);
fseek(fp1,0,SEEK_SET);
long long count = len /1024;
long long remain = len%1024;
for(long long i=0;i<count;i++)
{
fread(res, 1, sizeof(res), fp1);
fwrite(res, 1, sizeof(res), fp2);
onProgress(((float)((i+1)*sizeof(res)))/(float)len);
}
memset(res,0,sizeof(res));
if(remain)
{
fread(res, 1, remain, fp1);
fwrite(res, 1, remain, fp2);
onProgress(1);
}
fclose(fp1);
fclose(fp2);
}
void CopyFile::onProgress(float value)
{
list<IProgress*>::iterator itor = m_listIProgress.begin();
while(itor != m_listIProgress.end())
{
(*itor)->DoProgress(value);
++itor;
}
}
拷贝文件类定义了一个拷贝文件函数,拷贝文件函数我用的是标准文件IO,进行文件复制操作。我这个程序是用QT开发的,为什么我不用QT的接口操作,而用标准文件IO操作,是因为这样设计可以最大程度把代码复用,我这个类可以放在其它C++平台使用编译。然后还定义了一个存放通知者的容器。通过这个通知者容器去调用通知者各自的处理进度的方法。而我这个类不用具体去关心进度如何显示,只需要调用对应的接口就行了,这样就和进度显示的代码解耦合了。 五、普通进度条对话框类(class Dialog_progressBar) 头文件:
#ifndef DIALOG_PROGRESSBAR_H
#define DIALOG_PROGRESSBAR_H
#include "iprogress.h"
#include <QDialog>
namespace Ui {
class Dialog_progressBar;
}
class Dialog_progressBar : public QDialog, public IProgress
{
Q_OBJECT
public:
explicit Dialog_progressBar(QWidget *parent = 0);
~Dialog_progressBar();
virtual void DoProgress(float value);
signals:
void sig_Progress(float value);
private:
Ui::Dialog_progressBar *ui;
};
#endif
#include "dialog_progressbar.h"
#include "ui_dialog_progressbar.h"
#include <QDebug>
Dialog_progressBar::Dialog_progressBar(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog_progressBar)
{
ui->setupUi(this);
setModal(false);
}
Dialog_progressBar::~Dialog_progressBar()
{
delete ui;
}
void Dialog_progressBar::DoProgress(float value)
{
int progressBarValue = value*100;
QMetaObject::invokeMethod(ui->progressBar, "setValue",
Q_ARG(int, progressBarValue));
if(!this->isVisible())
{
QMetaObject::invokeMethod(this, "show");
}
if(value == 1)
{
QMetaObject::invokeMethod(this, "hide");
}
}
这里没什么好说的,就是重新实现了void DoProgress(float value)这个虚函数。就是要注意一下,这里由于我用到了多线程去处理拷贝文件,这个void DoProgress(float value)在另外一个线程执行的。而进度条对话框是在主线程创建的,所以不能直接调用界面的槽函数,直接调用程序会崩溃的。只能通过 QMetaObject::invokeMethod这种方式去调用。 六、拷贝完成后音效类(class MusicNotice) 头文件:
#ifndef MUSICNOTICE_H
#define MUSICNOTICE_H
#include "iprogress.h"
class QMediaPlayer;
class MusicNotice : public IProgress
{
public:
MusicNotice();
~MusicNotice();
virtual void DoProgress(float value);
private:
QMediaPlayer *m_pPlayer;
};
#endif
源文件:
#include "musicnotice.h"
#include <QMediaPlayer>
MusicNotice::MusicNotice()
{
m_pPlayer = new QMediaPlayer();
}
MusicNotice::~MusicNotice()
{
m_pPlayer->deleteLater();
}
void MusicNotice::DoProgress(float value)
{
if(value == 1)
{
m_pPlayer->setMedia(QUrl::fromLocalFile("./sound.mp3"));
m_pPlayer->setVolume(50);
m_pPlayer->play();
}
}
实现virtual void DoProgress(float value);下载完成后播放音乐。 七、拷贝管理类,配合QThread使用(CopyObject) 头文件:
#ifndef COPYOBJECT_H
#define COPYOBJECT_H
#include "copyfile.h"
#include "dialog_progressbar.h"
#include <QObject>
class CopyObject : public QObject
{
Q_OBJECT
public:
explicit CopyObject(QObject *parent = 0);
inline CopyFile *getCopyFileObj(){return &m_copyFile;}
public slots:
void doCopy(QString srcFileName, QString descFileName);
signals:
public slots:
private:
CopyFile m_copyFile;
};
#endif
源文件:
#include "copyobject.h"
#include "copyfile.h"
CopyObject::CopyObject(QObject *parent) : QObject(parent)
{
}
void CopyObject::doCopy(QString srcFileName, QString descFileName)
{
m_copyFile.doCopy(srcFileName.toLocal8Bit().data(),
descFileName.toLocal8Bit().data());
}
这个主要是配和QT的多线程使用而定义一个继承QObject的类。 八、主界面类 头文件:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "copyfile.h"
#include "iprogress.h"
#include <QMainWindow>
#include <QThread>
class QProgressBar;
class IProgress;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void sig_copyFile(const QString& srcFileName, const QString& descFileName);
private slots:
void on_pushButton_import_clicked();
void on_pushButton_copy_clicked();
private:
Ui::MainWindow *ui;
QThread copyThread;
IProgress *m_pNotice = nullptr;
IProgress *m_pDialog_progressBar;
};
#endif
源文件:
#include "copyobject.h"
#include "mainwindow.h"
#include "musicnotice.h"
#include "ui_mainwindow.h"
#include "dialog_progressbar.h"
#include <QMessageBox>
#include <QFileDialog>
#include <QProgressBar>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_pNotice = new MusicNotice;
m_pDialog_progressBar = new Dialog_progressBar(this);
CopyObject *pCopyObject = new CopyObject;
if(pCopyObject->getCopyFileObj())
{
pCopyObject->getCopyFileObj()->addNotice(m_pDialog_progressBar);
pCopyObject->getCopyFileObj()->addNotice(m_pNotice);
}
pCopyObject->moveToThread(©Thread);
connect(©Thread, &QThread::finished, pCopyObject, &QObject::deleteLater);
connect(this, &MainWindow::sig_copyFile, pCopyObject, &CopyObject::doCopy);
copyThread.start();
}
MainWindow::~MainWindow()
{
delete ui;
delete m_pNotice;
copyThread.quit();
copyThread.wait();
}
void MainWindow::on_pushButton_import_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("打开文件"),
"./",
tr(" (*.*)"));
if(!fileName.isEmpty())
{
ui->lineEdit_src->setText(fileName);
}
}
void MainWindow::on_pushButton_copy_clicked()
{
QString dir = QFileDialog::getExistingDirectory(this, tr("打开文件夹"),
"./",
QFileDialog::ShowDirsOnly
| QFileDialog::DontResolveSymlinks);
if(!dir.isEmpty())
{
ui->lineEdit_des->setText(dir);
if(ui->lineEdit_src->text().isEmpty())
QMessageBox::warning(this,tr("拷贝出错"),tr("未选择源文件"));
else
{
QString descFileName = ui->lineEdit_des->text() + "/copyfile";
emit sig_copyFile(ui->lineEdit_src->text(),descFileName);
}
}
else
{
QMessageBox::warning(this,tr("拷贝出错"),tr("未选择目标文件夹"));
}
}
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
这里讲一下使用多线程的好处,由于拷贝文件是一个耗时操作,尤其是拷贝大文件,很容易堵塞界面,用多线程就能很好的解决这个问题。
八、界面
九、实际运行效果 复制完成的音乐播放没办法在网页展示出来,大家可以把我代码拷贝过去用QT运行一下,看一下实际的运行效果。 十、总结 其实学设计模式主要要学会如何把代码模块块、类化,一个功能一个类,每个类之间不要有很强的联系,不要把所有的功能都集中在一个类,尤其是集中在主界面类。界面就是界面,只是用来和用户进行交互的,不要把逻辑功能都耦合在界面类里面,这样你的代码就很不好复用了。 并且我们看到用到观察模式以后,多个进度如何处理的类可以交给多人去写,并行进行代码编写工作,比如在一个团队里面,程序框架师把这个观察者模式的框架搭起来了,员工甲可以写进度条动画显示,员工乙可以写音效进度条。两个人可以同时开发,互不干扰。极大的节省了开发时间,提高了工作效率。还有就是用户有一个新的进度通知显示需求,对于已经发布的程序来说,不可能重新修改原有的代码,重新编译部署测试,再发布。使用观察者模式就可以只增加一个类,把这个类编译成动态库,再通过修改程序的配置文件来动态加载新增的动态库,这个就极大的扩展了程序,而不需要重新编译主程序界面代码,重新部署编译发布主程序界面,极大的降低了每个功能和主界面程序的耦合性。
|