在没有额外代码的情况下链接两个独立类的最通用方法是什么?



有一个描述某些对象不同属性的类层次结构。抽象类Property是一个基类,它有子类:IntegerPropertyBooleanProperty等。所有数据都以QString编码,派生类以自己的方式对其进行解码。

class Property : public QObject
{
Q_OBJECT
public:
// ...
virtual QString value() const = 0;
virtual bool setValue(const QString &value) = 0;
virtual bool validateValue(const QString& value) = 0;
// ...
};
class IntegerProperty : public Property
{
// ...
virtual QString value() const override;
virtual bool setValue(const QString &value) override;
virtual bool validateValue(const QString& value) override;
// ...
};
// ...

每个属性类都必须有一个独立的编辑器(GUI 小部件(-PropertyEditor(再次是抽象类(、IntegerPropertyEditorBooleanPropertyEditor等。

class PropertyEditor : public QWidget
{
Q_OBJECT
public:
inline Facer::PropertyPointer attachedProperty() { return m_property; }
protected:
PropertyEditor(Facer::PropertyPointer attachedProperty, QWidget* parent = nullptr);
virtual void mousePressEvent(QMouseEvent* event) override;
virtual bool eventFilter(QObject *watched, QEvent *event) override;
// ...
};
class IntegerPropertyEditor : public PropertyEditor
{
// ...
};
// ...

例如,我有一组不同的属性。我不知道我到底有什么属性,因为它们都是指向Property类的指针。我的任务是创建这些属性的指定编辑器,因此我需要获取IntegerPropertyEditor属性对象是否IntegerProperty

for (Property* property : propertySet())
PropertyEditor* editor = createEditor(property);

我用宏做了一个临时解决方法:

#define IF_TYPE_GET_EDITOR(propertyType, editorType) 
if (std::dynamic_pointer_cast<propertyType>(property)) 
return new editorType(property, this);
// ...
PropertyEditor *PropertySetWidget::create(PropertyPointer property)
{
IF_TYPE_GET_EDITOR(BooleanProperty, BooleanPropertyEditor)
else IF_TYPE_GET_EDITOR(ColorProperty, ColorPropertyEditor)
else IF_TYPE_GET_EDITOR(FloatingPointProperty, FloatingPointPropertyEditor)
else IF_TYPE_GET_EDITOR(FontProperty, FontPropertyEditor)
else IF_TYPE_GET_EDITOR(IntegerProperty, IntegerPropertyEditor)
else IF_TYPE_GET_EDITOR(TextProperty, TextPropertyEditor)
else throw std::runtime_error("This PropertyType is not implemented yet");
}

它看起来不是一个好的解决方案 - 如果我添加新类型的属性及其编辑器,我也必须更新此代码。链接编辑器类和属性类的最方便和通用的方法是什么?

这可能会提供一些额外的代码,特别是取决于项目的设置方式,但一种解决方案是在Property中创建一个返回指向编辑器的指针的虚拟函数:

class Property : public QObject
{
public:
virtual PropertyEditor* CreateEditor(PropertySetWidget* widget) {
// let's put our default behavior here
throw std::runtime_error("This PropertyType is not implemented yet");
}
//...
};

现在,你让每个类负责提供自己的编辑器:

class IntegerProperty : public Property
{
public:
// doesn't have to be virtual, I don't think Just a regular version should be fine too.
virtual PropertyEditor* CreateEditor(PropertySetWidget* widget) {
return new  IntegerPropertyEditor(this, widget);
}
//...
};

根据您有多少类,这可能是大量的复制和粘贴。

但是,有趣的部分PropertySetWidget::create()

PropertyEditor *PropertySetWidget::create(PropertyPointer property)
{
return property->CreateEditor(this);
}

因为property的每个孩子都有责任提供自己的编辑器,所以在这个级别上我们不必担心它。如果一个不存在/未实现,property::CreateEditor()会为你抛出一个错误。如果确实存在/已实现,它将自动返回指向编辑器新实例的指针。

最大的优点是,如果您添加新属性及其编辑器,则根本不需要触摸此功能。虚拟功能负责为您正确实现它。如果新属性具有编辑器,则只需重载该函数,此create()仍然可以正常工作。

当然,您将不得不以这种方式修改Property的界面,这在您的情况下可能不可行。这是这种方法的主要缺点。

你想要的需要实现反射,尽管有一些相当繁琐和丑陋的方法可以在没有宏的情况下实现你想要的东西。我个人推荐@Chipster的解决方案。

如果您仍然对不需要 Property 提供自己的编辑器的方法感兴趣......我写了一个例子,你可以看看。

#include <iostream>
#include <memory>
class A
{ //virtual working class
public:
virtual ~A() = default;
};
//two possible implementations
class B : public A {};
class C : public A {};
//Editor interface
class EditorA
{
public:
virtual ~EditorA() = default;
virtual void print() = 0;
};
//Implementations of editors
class EditorB : 
public EditorA
{
public:
void print() override
{
std::cout << "Editor Bn";
}
};
class EditorC : 
public EditorA
{
public:
void print() override
{
std::cout << "Editor Cn";
}
};
//template class used for declaring which Editor you use depending on the class you provide
// I would make a namespace but there are no template namespaces
template<typename T>
class EditorT;
template<>
class EditorT<B>
{
public:
using EditorType = EditorB;
};
template<>
class EditorT<C>
{
public:
using EditorType = EditorC;
};

using namespace std;
// Recursive GetEditor code... written inside class as a static method for reasons.
template<typename... Args>
class CEditorIdentifier;
template<>
class CEditorIdentifier<>
{
public:
static EditorA * GetEditor(shared_ptr<A>& val)
{
return nullptr;
}
};
template<typename Arg, typename... Args>
class CEditorIdentifier<Arg, Args...>
{
public:
static EditorA * GetEditor(shared_ptr<A>& val)
{
if(std::dynamic_pointer_cast<Arg>(val))
{
return new typename EditorT<Arg>::EditorType;
}
return CEditorIdentifier<Args...>::GetEditor(val);
}
};

template<typename... Args>
EditorA* FindEditor(shared_ptr<A>& val)
{
return CEditorIdentifier<Args...>::GetEditor(val);
}
int main()
{
shared_ptr<A> b = make_shared<B>();
shared_ptr<A> c = make_shared<C>();
EditorA* eB = FindEditor<B,C>(b);
EditorA* eC = FindEditor<C,B>(c);
eB->print();
eC->print();
return 0;
}

现在,您可以添加其他类DEF...你只需要维护引用类EditorT<D>EditorT<E>EditorT<F>...

很复杂吧?井。。。C++此类编程的当前功能有限。它正在工作,将来会可用(参见反射 TS(,但不是现在。此外,在 C++20 中实现会更简单,所有扩展都constexpr.

我喜欢上面关于每个属性都有一个虚拟方法来返回适当类型的编辑器的答案。唯一的缺点是它可能会将与用户界面相关的元素绑定到您的较低级别的代码中。根据您的需求,这可能没问题,也可能不行。

原始工厂的一个变体是,您可以将编辑器创建与属性类定义分开,您可以添加一个返回整数的"propertyType"虚拟方法,然后工厂将成为 switch 语句:

switch (Property.propertyType ())
{
case BooleanPropertyType: create Boolean property editor
case StringPropertyType:  create String properly editor
etc.
}

您将在某处有一个具有定义的属性类型值的枚举。这是相同的基本思想,但它避免了动态演员的开销。(它是否真的更快是需要测试的。

除了可能的dynamic_cast开销之外,我认为您的方法没有任何根本性问题,而且通常,我认为拥有一个为所有类型创建所有编辑器的工厂方法比在您尝试管理数据的类中创建 UI 元素更容易维护。纯粹主义者可能会认为这违反了良好的面向对象类,但这实际上取决于你的需求以及你想取悦谁。