Helper辅助类设计技巧
需求
当点击一个按钮后,执行功能代码。按钮文字为“执行”,代码执行成功后按钮闪烁显示“成功”,失败闪烁显示“失败”。闪烁完成后还原显示“执行”。
注:平台为Qt。
基础版
按照常规思路,点击按钮后,设置按钮文字,启动一个定时器。在定时器中刷新按钮字体颜色,记录定时器执行次数,完成后还原按钮文字。废话少说,上代码:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0)
:QMainWindow(parent)
{
ui.setupUi(this);
}
~MainWindow(){}
private slots:
void on_pushButton_clicked()
{
bool success = true;
ui.pushButton->setText(success ? "成功" : "失败");
m_times = 10;
flash();
}
void flash()
{
static bool b = true;
b = !b;
m_times--;
if (m_times > 0)
{
ui.pushButton->setStyleSheet(b ? "color:red;" : "color:blue;");
QTimer::singleShot(100, this, SLOT(flash()));
}
else
{
ui.pushButton->setText("执行");
ui.pushButton->setStyleSheet( "");
}
}
private:
Ui::MainWindowClass ui;
int m_times;
};
进阶版
上面的代码能够很好地工作,但是却存在几个问题:
- 如果另外一个按钮也需要同样的功能,我们必须再次写同样的代码,代码量线性增加。
- 如果这些代码有bug,修复bug的代码是成线性关系的。
针对这个问题,我们第一直觉当然是实现一个可以有闪烁功能的按钮类:
class FlashButton :public QPushButton
{
Q_OBJECT
public:
FlashButton(QWidget* parent = nullptr)
:QPushButton(parent){}
public slots:
void flash(const QString& text)
{
m_times = 10;
m_srcText = this->text();
flash();
}
private slots:
void flash()
{
static bool b = true;
b = !b;
m_times--;
if (m_times > 0)
{
this->setStyleSheet(b ? "color:red;" : "color:blue;");
QTimer::singleShot(100, this, SLOT(flash()));
}
else
{
this->setStyleSheet("");
setText(m_srcText);
}
}
private:
int m_times;
QString m_srcText;
};
然后之前的代码就像这样:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0)
:QMainWindow(parent)
{
ui.setupUi(this);
m_button = new FlashButton(this);
m_button->setText("执行");
ui.verticalLayout->addWidget(m_button);
}
~MainWindow(){}
private slots:
void on_pushButton_clicked()
{
bool success = true;
m_button->flash(success ? "成功" : "失败");
}
private:
Ui::MainWindowClass ui;
FlashButton* m_button;
};
相比基础版,主程序的代码少了许多,并且逻辑也更清晰。最重要的是解决了基础版存在的问题。
Helper版
但是,进阶版的代码同样存在一些问题:
- 我们只能在代码中创建我们的按钮,没法在Qt Designer中进行布局,这是一件很不爽的事情。
- 对于现有的代码,我们想增加此功能的代码也很大,尤其是需要修改的按钮如果也是一个QPushButton的子类的时候。
那如果很好地解决这些问题呢?叮咚,Helper版闪亮登场!
class FlashButtonHelper : public QObject
{
Q_OBJECT
public:
FlashButtonHelper(QPushButton* button)
:QObject(button), m_button(button){}
public slots :
void flash(const QString& text)
{
m_times = 10;
m_srcText = m_button->text();
flash();
}
private slots:
void flash()
{
static bool b = true;
b = !b;
m_times--;
if (m_times > 0)
{
m_button->setStyleSheet(b ? "color:red;" : "color:blue;");
QTimer::singleShot(100, this, SLOT(flash()));
}
else
{
m_button->setStyleSheet("");
m_button->setText(m_srcText);
}
}
private:
QPushButton* m_button;
int m_times;
QString m_srcText;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0)
:QMainWindow(parent)
{
ui.setupUi(this);
}
~MainWindow(){}
private slots:
void on_pushButton_clicked()
{
bool success = true;
FlashButtonHelper* flasher = new FlashButtonHelper(ui.pushButton);
flasher->flash(success ? "成功" : "失败");
}
private:
Ui::MainWindowClass ui;
};
这样我们又可以在Qt Designer中愉快地进行界面布局了,并且如果需要对现有代码增加功能,我们需要做的工作是否简单,在需要的地方添加如下代码即可:
FlashButtonHelper* flasher = new FlashButtonHelper(button);
flasher->flash("成功");
优化改进
对于大项目,很多时候需要对代码精雕细琢。在上面的代码中,如果我们点击按钮100次,那么就会有100个Helper类创建,这并不是我们想要的。因为其实只需要一个Helper即可(当然可以在适当的时候销毁,但是我们并不想在主程序中增加代码)。单例类为我们提供了解决思路,创建是首先判断对象是否存在,存在则返回存在的的对象即可:
class FlashButtonHelper : public QObject{
Q_OBJECT
public:
~FlashButtonHelper(){ instances()->remove(m_button); }
static FlashButtonHelper* getInstance(QPushButton* obj){
if (!instances()->contains(obj)){
(*instances())[obj] = new FlashButtonHelper(obj);
}
return (*instances())[obj];
}
public slots :
void flash(const QString& text)
{
m_times = 10;
m_srcText = m_button->text();
flash();
}
private slots:
void flash()
{
static bool b = true;
b = !b;
m_times--;
if (m_times > 0)
{
m_button->setStyleSheet(b ? "color:red;" : "color:blue;");
QTimer::singleShot(100, this, SLOT(flash()));
}
else
{
m_button->setStyleSheet("");
m_button->setText(m_srcText);
}
}
private:
FlashButtonHelper(QPushButton* button)
: QObject(button), m_button(button){}
static QHash<QPushButton*, FlashButtonHelper*>* instances(){
static QHash<QPushButton*, FlashButtonHelper*> s_instances;
return &s_instances;
}
private:
QPushButton* m_button;
int m_times;
QString m_srcText;
};
#define FLASH_HELPER(btn) FlashButtonHelper::getInstance(btn)
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0)
:QMainWindow(parent)
{
ui.setupUi(this);
}
~MainWindow(){}
private slots:
void on_pushButton_clicked()
{
bool success = true;
FLASH_HELPER(ui.pushButton)->flash(success ? "成功" : "失败");
}
private:
Ui::MainWindowClass ui;
};
关键点:
- 采用静态hash表保存所有的Helper对象。
- Helper构造函数将自己加入hash表,析构时从hash表中移除。
- Helper构造函数私有化,由getInstance执行是否创建Helper对象。
- Helper采用Qt的QObject进行内存管理。在目标对象销毁时自动销毁自己。
- 定义了FLASH_HELPER让代码更易读。
结语:上面的FlashHelper基本上就是一个Helper类的标准模板。俺就是抛砖一下,相信大家可以举一反三,设计更多的属于自己的优雅的Helper了。