2

背景:

  • 我有我的类ObjectListModel,它继承QAbstractListModel并包含一个QObjectList. 对象是行,它们的属性是列(使用 a 设置QMetaObject),并且通知更改会传播到视图。还有一些容器助手(开始/结束/迭代器/大小),以便我可以遍历存储的 QObject。
  • 我也有一个TypedObjectListModel<T>,它提供类型安全(主要是通过覆盖push_backet.al. 并定义对 T起作用的新iterator类型)。static_cast

当我只有一种类型的对象时,这一切都很好。我只是创建新类 (f.ex. FruitsModel,其中包含Q_OBJECT并继承TypedObjectListModel<Fruit>。这只能包含 Fruits 或 Fruit-subobjects。

但是,我现在有一个可以在两种不同状态下运行的应用程序。在第二种状态下,模型应该只包含 Apples,而不是 Bananas(或 Fruits,这是一个具体的基类)。

所以,我想创建一个ApplesModel类型,它应该继承FruitsModel并只是改变 T 的类型。这让我遇到了麻烦,因为我得到了继承钻石OF DEATH

 QObject
       |
 QAbstractListModel
       |
 ObjectListModel -------------------
       |                           |
 TypedObjectListModel<Fruit>     TypedObjectListModel<Apple>
       |                           |
 FruitsModel  -------------------ApplesModel

这在概念上也是错误的,因为 FruitsModel::push_back(Fruit*) 在 ApplesModel 中是非法的。但是,应该可以读取/迭代 Fruits(不仅仅是 Apples)。

此外,我在 FruitsModel ( findFruitById) 中有一些函数应该被覆盖,并且只返回 ApplesModel 中的 Apples。

在 C++ 中解决这个问题的首选设计模式是什么?

我怀疑(希望)我不是第一个尝试做类似事情的人。

我尝试了很多想法,但我陷入了各种死胡同。你会认为 ObjectListModel 的虚拟继承可以解决这个问题,但后来我得到了这个QObject::findChild

error C2635: cannot convert a 'QObject*' to a 'ApplesModel*'; conversion from a virtual base class is implied

上述问题可以通过我自己的 findChild 实现来解决,改用 dynamic_cast,但仍有一些死胡同。

template<typename T>
inline T myFindChild(const QObject *parent, const QString &name = QString())
{ 
    return dynamic_cast<T>(qt_qFindChild_helper(parent, name, reinterpret_cast<T>(0)->staticMetaObject)); 
}

更新

geekp 有以下建议:

从 Fruit 继承 Apple 不打扰 ApplesModel

然后我如何强制只有苹果在 FruitsModel 中?另外,每次我拿一个苹果(作为水果)时,我都需要沮丧。

不要从 FruitsModel 继承(如果你不使用它的方法,你为什么要这样做?)

我正在使用一些方法,尤其是那些用于阅读的方法。

不要从 Apple 的 TypesObjectListModel 继承,只继承 FruitsModel。

与不使用 AppleModel 的缺点相同。

4

2 回答 2

2

因此,读取和写入操作在继承方面是根本不同的。

回到 OOP 101,还记得关于正方形和长方形的比喻吗?人们常说正方形是长方形的一种,但只有在阅读时才如此。

写的时候,正方形不是长方形的种类,但长方形是正方形的种类!

IE:

bool test( Rectangle* r ) {
  int old_height = r->GetHeight();
  int old_width = r->GetWidth();
  r->SetWidth(old_width+100);
  return old_height == r->GetHeight();
}

上述函数返回true所有“真实”矩形,但Squares可能不会。SetWidth因此,对于 合理 的周围的合同Rectangle被违反Square

另一方面,每个Rectangle只读接口都由Square.

这可能会给你带来这样的混乱:

struct IRectangleRead { ... };
struct ISquareRead { ... };

struct ISquareWrite: virtual ISquareRead { ... };
struct IRectangleWrite:ISquareWrite, virtual IRectangleRead { ... };

struct ConstRectangle: virtual IRectangleRead { ... };
struct ConstSquare: virtual ISquareRead, virtual IRectangleRead { ... };

struct Rectangle: ConstRectangle, IRectangleWrite { ... };
struct Square: ConstSquare, ISquareWrite { ... };

这会产生一团糟的继承层次结构,但是可以在每个方法上放置限制性契约,并且实现该方法的每个对象都将遵守它们。

现在,您应该注意,如果您的对象是不可变的,那么上述操作会变得非常容易。那么唯一的写作形式就是通过工厂函数,事情就变得整洁了。

所以这里的具体课程 - 拆分代码的阅读和修改部分。通用修改部分(适用于基类)未公开公开,因为该操作在子类情况下无效。

普通阅读部分是公开的,亚型阅读部分也是如此。

子类型编写代码转发到私有公共基类编写代码。

于 2013-01-18T15:31:22.897 回答
0

多种选择:

  • 从 Fruit 继承 Apple 不打扰 ApplesModel
  • 不要从 FruitsModel 继承(如果你不使用它的方法,你为什么要这样做?)
  • 不要从 Apple 的 TypesObjectListModel 和子类继承 FruitsModel
于 2013-01-18T15:17:44.370 回答