本文只代表作者的观点和立场,在实际的实践过程中会受到主观意识引导,难免会和实际工作的方法论不相符合,出现错误与纰漏,还望各位读者阅读本文时多多海涵和不吝赐教。
先分析一下设计模式在项目流程中的作用,一个正常的公司项目大体流程一般为如下:
综上所诉,软件设计模式带来的好处就是,让你的代码有前瞻性,有限的程度上符合日益变更的需求,少些废代码,理解容易(业内潜规则),甩锅与装逼(这当然是说笑了)!!!
创建者模式:这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
我的第一个项目就是实现贴图的QListWidget其中不免含有item中元素的各种操作,比如单击事件双击和事件移动到上面会出现气泡什么的,下面以QQ群成员列表为例
这里看到成员2816个人,每人为每一项也就存在2816个项,每一项有三个元素,分别头像QLable,名称QLabel ,身份QLabel,不妨把Item作为工厂生产的产品,那么我们通过QString(头像路径),QString(成员名称),QString(身份标示)为一组生成不同产品(这里的不同指的是界面显示,而非class类的不同),下面我用伪代码说明。
typedef struct ItemMess
{
QString ImgPath;
QString name;
QString idImgPath;
}
QVector<ItemMess> MessContainer=
{
{"Path::一去二三里的头像","西安-一去二三里","Path::群主的图片"};
{"Path::北京-青春的头像","北京-青春","Path::管理员的图片"};
{"Path::上海-叶海的头像","上海-叶海","Path::管理员的图片"};
......;
} //这里定义一些初始化变量,然而在现实的需求中,肯定是来源数据库查询或者文件。
class Factory
{
public:
Factory(){}
QItemList generateItem(Vector<ItemMess> _itemMess)
{
QItemList itemlist;
foreach( auto var:_itemMess)
{
QItem pointer = new QItem() //构造新项
pointer.setHeadImg(QLabel::loadImg(var.ImgPath)); //设置新项的头像图片
pointer.setName(QLabel::loadText(var.name)); //设置新项的昵称
pointer.setIdImg( QLabel::loadImg(var.name)); //设置新项的身份图片
itemlist.append(pointer); //把新项添加到列表中
}
}
}
int main()
{
Factory f;
QListWidget w;
w.setItemList( f.generateItem(MessContainer));
w.show();
return 0;
}
上面 QItemList Factory::generateItem(Vector _itemMess) 把输入数据作为当前QItemList 生成的依赖信息,生成最终的产品QItemList 然后返回数据项设置到QListWidget。其中Factory 扮演的角色如下:
开始剖析抽象工厂模式,不得不提到实际项目中肯定会遇到的几点矛盾:
1.刚开始框架铺开,实现代码的前期,会遇到class实现的功能不明确。
2.实现代码存在多种泛化。
3.既定接口实现class,通常用在C++插件上(常说的面向接口编程)
实现抽象工厂的难点:使用场景有限,需要考虑的class兼容性,纯虚函数限制,继承抽象工厂模式的class tree都需要实现相关的方法(无论是继承树的哪一层级都需要重新实现),推荐如果出现第3种矛盾可以采用,如果想要更加深入的了解抽象工厂模式,建议查看我的另外一篇文章QtPlugin(C++跨平台插件开发),特别是代码的多种泛化,千万千万最好别用它,不然你会绞死在自己未定义的错误上面(当然)。当然有更好的使用抽象工厂模式也欢迎一起探讨,对于IT新人可能不太容易理解抽象工厂模式,于是斟酌了一下,就多写一些话,如果看不太明白的话这这个设计模式可以直接跳过,同时抽象工厂模式的不同场景,所以很多存在相似的地方,说明得详细一点也无伤大雅。
#ifndef QABSTRACTBUTTON_H
#define QABSTRACTBUTTON_H
#include
#include
#include
QT_BEGIN_NAMESPACE
class QButtonGroup;
class QAbstractButtonPrivate;
class Q_WIDGETS_EXPORT QAbstractButton : public QWidget
{
Q_OBJECT
more .....
protected:
void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE = 0;
virtual bool hitButton(const QPoint &pos) const;
virtual void checkStateSet();
virtual void nextCheckState();
more .....
};
QT_END_NAMESPACE
#endif // QABSTRACTBUTTON_H
直接上Qt自带的QAbstractButton文件可能稍微好理解一些,所有泛化的button class都继承QAbstractButton。相信在Qt官方组编写Button怎么实现不同类型的按钮也思考了这些问题,但是尽量会思考得完整一些。因为抽象嘛,到时候继承抽象类得实现一堆代码,能够略过抽象落到实现方法上是最好的。
上面是抽象类抽象方法的实现结构图,按照道理来说,我们并不知到我们要实现什么button的界面绘制效果(这里接触过Qt的同僚应该知道,点击了不同类型button在界面上的显示是不一样的),我们预留一个(当然可以多个)接口作为不同button自己的内部实现paintEvent()。这就是抽象到实例化方法,而QAbstractButton就是一个抽象工厂。
前段时间有一个伙计我现在实现了实例化的class,要怎么去用上抽象工厂模式呢,我的回答是知道实例没必要再反着推抽象了。如果是为了统一接口反过来用过面向接口编程这种抽象工厂也是没有错的。面向接口编程为了切合实际,我们首先先假定一个场景。场景如下:
我们想要做一款摄像头识别文字然后显示到任意的显示设备上。为了扩展摄像头的不同型号和和显示设备的不同型号,首先我们肯定过不会先去实现摄像头和显示设备的内部处理采集到的数据流代码。
enum DeviceType //设备类型枚举标记
{
DT_Camera = 0,
DT_Display
};
class AbstractDevice //通常接口会是叫做 class InterfaceDevice 这里定义成抽象工厂的名称实则是一样的
{
public:
virtual bool open() = 0;
virtual bool close() = 0;
virtual QByteArray readAll() = 0;
virtual DeviceType getDeviceType() = 0;
QString deviceName(){return m_StrDeviceName;}
private:
QString m_StrDeviceName; //不一定抽象工厂里面不能定义其他实际变量,因为经常继承可以会用到
};
/*定义了一个抽象的设备类型,假设我们不知道我们的设备到底是什么样的设备(包括摄像头和显示设备),而枚举类型的标记中没有
DT_Camera = 0,
DT_Display
整个DeviceType都为空,
*/
class AbstractCamera:public AbstractDevice //抽象摄像头类型
{
public:
DeviceType getDeviceType(){return DeviceType::DT_Camera;} //标记设备为摄像头
private:
};
class AbstractDisplay:public AbstractDevice
{
public:
DeviceType getDeviceType(){return DeviceType::DT_Display;}
};
class Camera_1080P:public AbstractCamera //最终实现1080p的摄像头
{
public:
Camera_1080P(){}
bool open(){}
bool close(){}
QByteArray readAll(){}
};
class Display_LED:public AbstractDevice //最终是实现LED的显示屏
{
public:
Display_LED(){}
bool open(){}
bool close(){}
QByteArray readAll(){}
};
为了加深映像我代码中用两次抽象class继承,你会发现载上面的class中除了Camera_1080P和Display_LED我都没有实现构造函数,因为接口的编写都是抽象的,不允许实际的构造。而继承到最终的实际设备中,我们实现了所有的抽象方法,从而产生了置顶而下的构造。对外的接口:
bool AbstractDevice::open();
bool AbstractDevice::close();
QByteArray AbstractDevice::readAll();
DeviceType AbstractDevice::getDeviceType();
QString AbstractDevice::deviceName();
那么疑问来了,我为什么采用了这种方式去抽象有继承实现?不得不提到的一个子类可以转父类型,然后通过父类的接口进行子类的调用,这也称作代码的闭包,相信都有所耳闻微服务框架,那么C++有没有呢?肯定是有的。那就是插件机制,通过把每一个class都封装成dll,然后通过顶层预留接口进行操作dll中的代码。调用流程图如下:
屏蔽编译器和C++的实现机制,上层结构如果用底层的汇编来杠那就没意思了,更为详细的dll封装移步QtPlugin(C++跨平台插件开发)或者百度CTK框架,你会学习到C++的微服务框架。
这个模式应该是广大同僚用的最多的。通过屏蔽对外的构造函数实现,场景不由分说,作用于当前程序只能存在一个class实例,经常用在管理器(批量的new/delete class)之上。
// ClassManager.h
class ClassManager: public QObject
{
public :
static const ClassManager* getInstance();
void ClassRegister(const QObject * pointer); //其他公有供调用的方法
private:
ClassManager(){}
~ClassManager(){}
static ClassManager* This;
}
//ClassManager.cpp
#include "ClassManager.h"
static ClassManager* ClassManager::This = nullptr;//在调用getInstance()时构造
static const ClassManager* getInstance()
{
if(!This) This = new ClassManager;
else return This;
}
void ClassRegister(const QObject * pointer)
{
//你的一些操作
}
懒汉单例可以用在Widget类型的组件,因为new Widget class 需要在 Application之后
// ClassManager.h
class ClassManager: public QObject
{
public :
static const ClassManager* getInstance();
void ClassRegister(const QObject * pointer); //其他公有供调用的方法
private:
ClassManager(){}
~ClassManager(){}
static ClassManager* This;
}
//ClassManager.cpp
#include "ClassManager.h"
static ClassManager* ClassManager::This = new ClassManager(); //直接构造
static const ClassManager* getInstance()
{
if(!This) This = new ClassManager
else return This
}
void ClassRegister(const QObject * pointer)
{
//你的一些操作
}
懒汉和饿汉只是进不进行class构造而已。
看了N多教程解释这个专业名词,感觉举例子都太抽象了,比如什么肯德基套餐,实际代码呢,就是实单一的class 然后通过实例化形成一个包含多单一class的复合class,在Qt中存在这样的例子,比如界面 QComBobox ,为什么点击后会出现下拉列表?这个下拉列表是一个QListWidget,而把一或者多个对象组合成一个复合对象的过程,叫做建造过程,把这种方法论叫做建造者模式。所有的设计模式都是方法论!!!
在项目中建造者模式用的比较多,我有一个自定义实现的QFileSelectBox组件类似CMake-GUI 的FileSelect具有编辑时检索本地文件并且补全到下拉列表的功能,代码在Github上(这里不方便贴代码,因为代码实在是太多了)这里是传送门,建议认真学习设计模式的看官能够下载然后跑一跑,认真的分析代码的实现。
下面我实现最终的代码,拥有注释部分是建造者模式中构造的单一Class
class FileSelectBox : public QWidget
{
Q_OBJECT
public:
enum FileSelectType
{
SELECT_ALL = 0,
SELECT_DIRS = 1,
SELECT_FILES = 2,
};
explicit FileSelectBox(QWidget *parent = nullptr);
FileSelectBox(FileSelectType type,QWidget *parent = nullptr);
~FileSelectBox();
const QLineEdit* lineEdit();
const QPushButton* pushButton();
const QTableView* tableView();
const QDialog* fileDialog();
void setLineEditText(QString Url);
void setPushButtonText(QString DisplayTest);
private:
FileSelectLine* m_pLineEdit = nullptr; //本质上是一个继承QLineEdit重构类型,为了实现的一些Signed
selectPopList* m_pTableView = nullptr; //本质上是一个QTableView,作为下拉列表的显示项
QPushButton* m_pPushButton = nullptr; //本质上是一个QPushButton,作为点击的按钮
QStandardItemModel* m_pItemModel = nullptr; //Item作为填充QTableView的数据项
FileSelectType m_eSelectFileType = SELECT_ALL;
QFileDialog* m_pFileDialog = nullptr;
int m_iItemWidth,m_iItemHeight = 30;
QPoint m_MovePos;
QPoint m_AfterPos;
virtual void resizeEvent(QResizeEvent* event);
virtual void keyPressEvent(QKeyEvent *event);
virtual void moveEvent(QMoveEvent* event);
virtual void paintEvent(QPaintEvent* event);
virtual void showEvent(QShowEvent *event);
virtual void hideEvent(QHideEvent *event);
virtual void closeEvent(QCloseEvent *event);
virtual void focusInEvent(QFocusEvent *event);
virtual void focusOutEvent(QFocusEvent *event);
void initWidget();
private slots:
void showPopList(QStringList strList);
void selectFile(QString path);
void showSelectFileDialog();
void hidePopList();
void enterKeyAddText();
signals:
public slots:
};
最后所有的类组合成为 FileSelectBox。这便是建造者模式
原型模式重点在于重载 operator = (),实现拷贝构造实现快速生成一个当前类的副本目标,什么?没听过?那就放弃看这篇文章。先去看看C++的基础。
经常用在class拷贝,貌似好像没有怎么实现过,因为都是传Class地址。这个模式实现的可能就只有Qt中的QString class还有带有拷贝构造的容器类型,有其他的class或者实现的场景欢迎在评论区留言。
题外话:拷贝构造会生成新的class对象,如果在class传递中默认使用了class的拷贝构造(例如QString),那么程序将会在调用函数时溢出。虽然是题外话,但是仍然是应该打星号的重点。
结构型模式:这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
假定一个场景:我们都知道QWidget类树族中Hide()和show()是隐藏和显示QWidget,基于这个方法做了一个QWidget界面显示和隐藏的管理类,原先的界面管理器结构,如下:
上面的结构可以控制所有Widget子类Hide和show。现在因为业务需求,引入了新的界面管理器(假设来自其他GUI界面框架)。
要求实现兼容两套界面管理器,如下(conceal:隐藏,spread:展示):
思维,构造QWidget的两个可兼容新管理类的接口函数
QWidget::conceal()
QWidget::spread()
但是我们总不能去更改QWidget源代码对吧,那么
QWidget::conceal() => Interface::conceal()
QWidget::spread() => Interface::::spread()
class Interface:public QWidget
{
public:
explicit interface(QWidget* parent = nullptr):QWidget(parent){}
~interface(){}
void conceal(){hide();}
void spread(){show();}
};
我所构造的 Interface 就是一个适配器,适配的 QWidget 类,作为 InterfaceManaegr 和 QWidgetManager 中间媒介。
我通过 Interface 类型转换到父类型 QWidget 注册到 QWidgetManager 以供 hide() 和 show() 的调用,我也可以通过Interface注册到InterfaceManager 以供 conceal() 和 spread() 的调用。
你想要所有继承QWidget 的类要在 InterfaceManager中能够调用 conceal() 和 spread() ,你得更改继承QWidget为Interface。并且单根多重继承会提示。
class Interface
{
public:
Interface(QWidget* instance = nullptr)
{
m_pAdapterWidget = instance;
}
~Interface(){}
void setAdapterWidget(QWidget* instance){m_pAdapterWidget = instance;}
QWidget * adapterWidget(){return m_pAdapterWidget;}
void conceal(){m_pAdapterWidget->hide();}
void spread(){m_pAdapterWidget->show();}
void hide(){m_pAdapterWidget->hide();}
void show(){m_pAdapterWidget->show();}
private:
QWidget * m_pAdapterWidget = nullptr;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Interface intfc(new QWidget);
intfc.spread();
return a.exec();
}
通过Interface(QWidget*) 转换成新的 class;
套壳实现 Interface 只能通过调用 QWidget * adapterWidget() 获取私有的 m_pAdapterWidget 设置到QWidgetManager,
而直接兼容新的InterfaceManager。
class Interface
{
public:
Interface(QWidget* instance = nullptr,QWidget* parent = nullptr)
{
if(parent) instance->setParent(parent);
if(instance) m_pAdapterWidget = instance;
}
~Interface(){}
void setAdapterWidget(QWidget* instance){m_pAdapterWidget = instance;}
QWidget * adapterWidget(){return m_pAdapterWidget;}
void conceal(){if(m_pAdapterWidget) m_pAdapterWidget->hide();}
void spread(){if(m_pAdapterWidget) m_pAdapterWidget->show();}
private:
QWidget * m_pAdapterWidget = nullptr;
};
class myButton :public Interface,public QWidget
{
public:
myButton(QWidget* parent = nullptr):
QWidget(parent),
Interface(nullptr,parent)
{
setAdapterWidget(this);
}
~myButton(){}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
myButton button;
button.spread();
return a.exec();
}
个人认为多重构继承的套壳实现看起来有鸡肋。
三种构造的方式的糙话体现:
根据上面的解析,我觉得最能让人接受的是套壳实现,你别管我爸爸妈妈爷爷奶奶,我就找一个朋友帮我插三孔插板。
上面讲了适配器模式,可能会有些疑问 QWidgetManager 和 InterfaceManager 是干嘛的?桥接模式整好分拣一下这一块,顺带,整好最近开发的项目中用到了此模式。
这里将会提到QSS和QSS选择器,在QtEvent中也运用了此设计模式进行事件分类与执行。
行为型模式:这些设计模式特别关注对象之间的通信。
这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。这些模式其实在编程语言中都有所体现,并不是Java所有物,Java中的 MVC 可能同样的设计模式在 Qt中体现为 MVD。
整过Qt的人都知道有个QDesigner,有和工厂模式看起来一些类似,但是本质上却有差距,QDesigner生成的QML文件更倾向容器和管理类型,为什么这么说呢,因为delete ui;会帮你回收载ui文件中定义的所有class;