如何正确组织我的继承类以利用多态性?

Posted

技术标签:

【中文标题】如何正确组织我的继承类以利用多态性?【英文标题】:How do I properly organize my inheritance classes to take advantage of polymorphism? 【发布时间】:2015-04-29 16:17:50 【问题描述】:

我正在尝试重新设计我的项目类。我无法想象事情应该如何运作。

当前实施:

class Item : public QGraphicsItem

public:
    typedef enum   PolygonType = QGraphicsItem::UserType + 1  ShapeType;
    Item() ... 
    Item(const Item &copyItem) // copy constructor
       m_shapeType = copyItem.getItemShape();
        m_color = copyItem.getItemColor();
        setPos(copyItem.pos()); ...
    QRectF boundingRect() const;
    QPainterPath shape() const;
    void setItemShape(ShapeType s)  m_shapeType = s; 
    ShapeType getItemShape() const  return m_shapeType; 
    int type() const  return m_shapeType;    // this will replace the above to allow QGraphicsItem casts
    void setItemColor(QColor c)     m_color = c; 
    QColor getItemColor() const     return m_color; 
    ....
protected:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
               QWidget *widget);
    QColor m_color;
    ShapeType m_shapeType;
    ....
;

QPainterPath Item::shape() const

    QPainterPath path;
    switch (m_shapeType)
    
    case Item::PolygonType:
        ...
        break;
    ....
    

QRectF Item::boundingRect() const

    return QRectF(...);

void Item::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/)

    switch (m_shapeType)
    
    case Item::PolygonType:
            painter->drawPolygon(shape().toFillPolygon());
            break;
    ....
    

根据选择,我创建一个项目并添加到画布...然后在完成所有更改后,我将其添加到 QGraphicsScene

所需任务:

场景的处理包含类型的迭代

Item* item1 = new Item(*renderArea->item);
// make some changes on the item copy, then add it to a scene
collectionView->scene()->addItem(item1);

// in another function...
for(int i = 0; i < collectionView->scene()->items().size(); ++i)
        
    Item* item = new Item(*(dynamic_cast<Item*>(
                           collectionView->scene()->items().at(i))));
   ...

我正在努力实现的目标

这是一种最小的方法,但我必须将功能扩展到扩展其他类的项目,例如 QGraphicsPixmapItem(不必为每个不需要它的项目添加像素图)或 QGraphicsSvgItem(同样,没有不得不为不需要它的项目增加如此多的开销)。

所以我正在尝试:

class PolyItem : public Item

public:
    PolyItem(): Item()
    
        m_shapeType = Item::PolygonType;
    
    PolyItem(PolyItem& copy): Item::Item(copy)
    
    
    virtual void paint(QPainter* painter,
                       const QStyleOptionGraphicsItem* option,
                       QWidget* widget = NULL)
    
        painter->drawPolygon(shape().toFillPolygon());
    
;

class PixMapItem : public Item, public QGraphicsPixmapItem

public:
    PixMapItem(): Item()
    
        m_shapeType = Item::PixmapType;
        setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
    
    PixMapItem(PixMapItem& copy): Item::Item(copy)
    
        setPixmap(copy.pixmap());
        setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
    
    virtual void paint(QPainter* painter,
                       const QStyleOptionGraphicsItem* option,
                       QWidget* widget = NULL)
    
        painter->drawPixmap(boundingRect(), pixmap());
    
;

问题:

如何使它工作?当我选择一个形状时如何创建 PolygonItem 的实例(并且仍然能够使用通用 Item 来修改其他属性,比如颜色?)

-----> 我正在考虑在 Item 类中创建一个创建正确形状的不同构造函数 - 但这似乎意味着 Item 需要类多边形,而该类多边形需要类 Item...循环依赖?

更复杂的是,我如何执行上述任务,修改通用项目,迭代项目,而不考虑形状?

我无法想象的:

collectionView-&gt;scene()-&gt;addItem(item1);

如果我的项目是多边形类型,将调用 Item::paint() 函数 - 以及所有其他相关函数 - 还是调用 PolyItem::paint() ? 我是否必须插入 switch-case 代码来确定和强制转换每种类型的每个实例?

【问题讨论】:

【参考方案1】:

可能值得将您的 Item 类简化为 BaseItem 包含您自己添加到 Item 的所有内容,以及您希望任何 Item 具有的所有功能(如果没有 QGraphics* 就无法实现它们,则为纯虚拟):

class BaseItem 
public:
    void setItemColor(QColor c)     m_color = c; 
    QColor getItemColor() const     return m_color; 
    ....
protected:
    QColor m_color;
    ShapeType m_shapeType;
;

然后

class Item : public BaseItem, public QGraphicsItem
class PixMapItem : public BaseItem, public QGraphicsPixmapItem

等等 - 避免循环依赖。任何Item都可以称为BaseItem,并提供BaseItem定义的通用接口。每个孩子都提供该接口的专业化,以适当的 QGraphics*。

您的原始设计的问题是该项目继承自 QGraphicsItem 而它不应该,因为您计划进一步将其专门用于 QGraphicsPixmapItem,它本身已经包含 QGraphicsItem。

编辑

现在,当您遍历它们时,您将所有项目视为 BaseItem - 这意味着 BaseItem 必须提供所有项目都遵循的接口。鉴于您在问题中提供的示例,一种方法:

class BaseItem 
  virtual void AddToScene(QGraphicsScene* scene) = 0;
  // Caller takes ownership.
  virtual BaseItem* CreateCopy() = 0;
;

所需任务:

场景的处理包含类型的迭代

BaseItem* item1 = renderArea->item->CreateCopy();
// make some changes on the item copy, then add it to a scene
item1->AddToScene(collectionView->scene());

每个派生类实现 接口由 BaseItem 定义,而您在通用操作中将所有 Items 视为 BaseItem。这也使代码易于扩展——添加新的 Item 类型只需要实现派生类,但现有代码保持原样——因此 BaseItem 需要仔细设计,为所有项目的通用操作提供接口。

class Item : public BaseItem, public QGraphicsItem 
      void AddToScene(QGraphicsScene* scene) override  scene->addItem(this); 
      // Caller takes ownership.
      BaseItem* CreateCopy() override  return new Item; 
;

【讨论】:

这就是我的想法——但class PixMapItem : public Item, public QGraphicsPixmapItem 不也包含QGraphicsItem 两次吗?因此,在现有 Item 类之上进一步专门化 pixmapitem 和 svgitem 类就足够了? (或者从技术上讲,我最好将BaseClass 作为接口?) 另外...我还是不明白怎么使用这些物品,在使用下摆之前我是否必须将它们投射到最幼稚的类(即添加到画布上)? 抱歉,打错字了——我指的是 BaseItem。 对不起,我只是没有看到它——我在这个例子中看到的唯一区别是你删除了ItemQGraphicsItem 的依赖。这将不再允许 scene() 识别 Item 对象 - 所以我不能再将通用项目添加到 scene() 或遍历 scene() 项目并提取 Item 类型。所以它的功能更少——至于创建正确类型的项目,没有办法。 @Thalia 扩展了一点【参考方案2】:

根据运行时决策创建不同的对象是Abstract factory pattern 的典型用例。在您的情况下,您可以:

class ItemFactory

public:
    virtual Item* create() = 0;    
;

然后:

class PixMapItemFactory : public ItemFactory

public:
    PixMapItemFactory(/* possibly some configuration parameters */);
    Item* create();  // will return a PixMapItem instance
;

然后您可以对ItemFactory* 类型进行操作,而不知道也不关心您正在创建的特定类型是什么。它将返回 Item* 实例,因此您可以更改它们的公共属性,而不管它们的具体实例类型如何。

【讨论】:

如何使用 Item* ?我是否能够使用我描述的功能 - 遍历 items() 集合并将它们转换为 Item* ?如果我转换为Item* Item 恰好是PixmapItem 类型,它是否具有PixmapItem 的功能? @Thalia 您不需要将它们转换为Item* - 它们已经是Item* 类型。您的代码不应将Item* 显式转换为子类型。在Item 中实现通用方法,在Item 中实现virtual/abstract 的行为,并在子类中覆盖它们。 子项的功能会使用吗?我只是不知道该怎么问——这确实是我唯一的问题。如果我使用“QGraphicsScene”来操作项目,会使用父或子paint() 方法吗?当我将一个项目添加到场景中时,我是否必须知道它是什么项目类型,或者即使我要求添加父项,也会添加子项? @Thalia 任何关于 C++ 多态性的最简单教程都以所有可能的方式回答了这个问题。如果方法是虚拟的,则将使用实际对象的方法,而不管指针类型如何。因此,如果Item* 指向PixMapItem 类型的对象,则将使用来自PixMapItem 的方法。

以上是关于如何正确组织我的继承类以利用多态性?的主要内容,如果未能解决你的问题,请参考以下文章

JAVA 作业 实验名称:接口,继承与多态

双继承和多态。如何从一个基类中拥有两个独立的成员?

设计模式之美——封装,继承,多态的意义

20172313 2017-2018-2 《程序设计与数据结构》第八周学习总结

python 全栈 python基础 (十六)面向对象编程的 继承 多态与多态性 封装

C中的继承和多态