利用Qt元对象技术防止工厂模式下代码臃肿问题,QT 动态创建对象(第2种方法)

部署运行你感兴趣的模型镜像

问题的提出:

   近来要编写一个仿真液压、电力、机械的软件,如下为液压的:

 可以看到液压图中很多液压元器件,这些元器件的id、名称等都是从json配置文件读取的,配置文件格式如下:

{
   "ClassName":"CComponentLineWidget",
  "Object": {
    "displayname": "直线",
    "drawoutline": false,
    "id": "66c7673c-2826-339e-4b38-a67f840680bd",
    "linetype": 1,
    "pencolor": "#000000",
    "penwidth": 4,
    "treeicon": "直线.svg",
    "viewicon": "直线.svg",
    "growstep": 1,
    "points": ""
  }

}

其中ClassName是实现该元器件的类 ,如上面json中的CComponentLineWidget就是实现“直线”的类,"Object"节点是直线的属性。

最初的设想是:

定义一个CComponentWidget的基类,其它如上图液压业务中的各种油缸、电磁阀、压力表等都从 基类派生。然后定义一个类似如下的工厂模式,new出各种控件,如下:

CComponent* CComponent::createComponentFactory(const QString& strComponentId)
{
   if (!strComponentId.compare(g_pszComposeComponentId, Qt::CaseInsensitive))     
	{
		pComponent = new CComposeComponent(strComponentId);
	}
 
	else if (!strComponentId.compare(g_pszValveComponentId, Qt::CaseInsensitive))        
	{
		pComponent = new CSingleValve(strComponentId);
    }
	else if (!strComponentId.compare(g_pszControlValveComponentId, Qt::CaseInsensitive))      
	{
		pComponent = new CControlValve(strComponentId);
	}
 
	else if (!strComponentId.compare(g_pszHighPressureBallValveComponentId, 
    Qt::CaseInsensitive))       
	{
		pComponent = new CHighPressureBallValve(strComponentId);
	}
  

   ........                   // 这里还有好多各种元器件的构建代码
}

其中CComponentWidget为元器件的基类,CComposeComponent、CSingleValve、CControlValve等都是表示上图的元器件实现类,是派生自CComponent的子类。

      现实的情况是:单单就液压业务,各种元器件都有60多个,再加上后期的电力、机械等,各种元器件不下200种,那么这个工厂模式下的if、else将会无比巨大,且更要命的是:每种元器件的属性要弹出对话框,属性对话框中的属性就是读自上面json配置文件中的"Object"节点,以便实现让用户设置元器件属性,用户选择好后,这些属性要保存到本地,即更新上面json配置文件中的"Object"节点,后期加载时,这些用户设置好的属性要附加到元器件上来,这么下来,200多个元器件的属性设置、保存、加载也是巨大的工作量,为了解决这个问题,我们采用Qt的元对象技术,只要十几行代码就可以搞定工厂模式下的数百个if、else才搞定的工作。下面详细说明:

1)在基类CComponentWidget中定义一个构建各种子类对象的静态方法,如下:

// 加载
CComponentWidget* CComponentWidget::load(const QJsonObject& json)
{
	QString className = json["ClassName"].toString(); // (1)
	if (className.isEmpty())
		return nullptr;
	int type = QMetaType::type((className + "*").toLatin1());  // (2)
	if (type == 0)
	{
		assert(0);
		return nullptr;
	}
	const QMetaObject* metaObj = QMetaType::metaObjectForType(type);// (3)
	if (metaObj == nullptr)
	{
		assert(0);
		return nullptr;
	}
	QObject* obj = metaObj->newInstance(); // (4)
	if (obj == nullptr)
	{
		assert(0);
		return nullptr;
	}
	auto objHash = json["Object"].toObject().toVariantHash();
	for (auto iter = objHash.constBegin(); iter != objHash.constEnd(); ++iter) // (5)
	{
		obj->setProperty(iter.key().toStdString().c_str(), iter.value());
	}

	return dynamic_cast<CComponentWidget*>(obj);
}

该段代码说明如下:

(1)句代码表示从json中读取ClassName类的名称,也就是元器件的实现类,这里是直线的实现类        CComponentLineWidget。

(2)句代码是利用Qt的元类型类取出字符串:“CComponentLineWidget*”即指向 

     CComponentLineWidget类指针的句柄,说白了也就是返回一个

     CComponentLineWidget类的指针。

    请注意:要使该句代码作用,也就是type不返回QMetaType::UnknownType即0,必须在该load函数调用之前,利用qRegisterMetaType函数对要创建的类进行注册,否则type因为没有注册该类,找不到该类,从而返回返回QMetaType::UnknownType导致后续流程都不能执行。以直线元件为例,调用如下:

// 本句必须保证在load函数之前调用
qRegisterMetaType<CComponentLineWidget*>();

(3):根据返回的指向某个类的句柄创建 Qt的元对象metaObj。

(4):再调用Qt的元对象metaObj的newInstance()方法,此时会调用元器件类的构造函数。请注意:元器件的构造函数必须标识Q_INVOKABLE,否则不会调用,如下为Qt官方对newInstance()方法说明:

Note that only constructors that are declared with the Q_INVOKABLE modifier are made available through the meta-object system.

本例CComponentLineWidget构造函数声明如下:

Q_INVOKABLE CComponentLineWidget(QGraphicsItem *parent = nullptr)

(5):读取json中该元器件的各种属性到Qt元对象属性中,请注意:要使Qt元对象属性起作用,必须用Q_PROPERTY在类的声明文件的private区将属性标识出来,且要提供相应的set、get函数,具体请参考Qt帮助手册中Q_PROPERTY使用说明。在(5)这个for循环中会自动调用你写好的set函数,从而实现将json中的属性设置到元器件上。

以上是元器件的创建及属性加载,再来看元器件的属性保存,如下:

// 保存
void CComponentWidget::save(QJsonObject& json)
{
	QJsonObject obj;
	auto metaObject = this->metaObject();(1)
	for (int index = 0; index < metaObject->propertyCount(); ++index)(2)
	{
		auto metaProperty = metaObject->property(index);

		QString name = metaProperty.name();
		QVariant var = metaProperty.read(this);
		obj[name] = QJsonValue::fromVariant(var);
	}
	json["Object"] = obj;
	json["ClassName"] = metaObject->className();
}

这个就很简单了,(1)句代码就去取出元器件实现类的元对象,然后变量对元对象的所有属性遍历、更新相应的json属性节点。然后在元器件基类即CComponentWidget中相应双击事件,弹出属性对话框,如下:

,QT 动态创建对象(第1种方法)参见:QT 动态创建对象(第一种方法)_danshiming的博客-优快云博客在我继续一系列的Qt数据序列化文章之前,有一个相对重要的需要提及的话题,那就是:基于类名动态创建类对象的能力。 假定现在我们要创建一系列的形状,形状是一个抽象类,实际类是存储在一个列表中的各种各样的派生类:矩形、圆等等。在序列化期间,我们可以保存每一项的类名和对象数据,在反序列化(即加载数据)时,我们需要能够创建合适类实例的能力,这就是要用到一个对象工厂的地方。在支持反射的语言中,例如C#、Java,仅需要几行代码就可以从一个跟定的类名字符串获得一个类实例。但是在c++中没有这样的机制。...https://blog.youkuaiyun.com/danshiming/article/details/119923840

您可能感兴趣的与本文相关的镜像

Llama Factory

Llama Factory

模型微调
LLama-Factory

LLaMA Factory 是一个简单易用且高效的大型语言模型(Large Language Model)训练与微调平台。通过 LLaMA Factory,可以在无需编写任何代码的前提下,在本地完成上百种预训练模型的微调

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值