我如何正确组织我的继承 类 以利用多态性?
How do I properly organize my inheritance classes to take advantage of polymorphism?
我正在尝试重新设计我的商品 class。我无法想象事情应该如何运作。
当前实施:
class Item : public QGraphicsItem
{
public:
typedef enum { PolygonType = QGraphicsItem::UserType + 1 } ShapeType;
Item() {...}
Item(const Item ©Item) // 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;
....
}
}
根据选择,我创建了一个项目并添加到 canvas...然后在完成所有更改后,我将其添加到 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))));
...
}
我想要达到的目标
这是一个最小的方法,但我必须将功能扩展到扩展其他 classes 的项目,例如 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 来修改其他属性,比如颜色?)
-----> 我正在考虑制作一个不同的构造函数来创建正确的形状,在 class Item - 但这似乎意味着 Item 需要 class 多边形需要 class 项目...循环依赖?
- 更复杂的是,我如何执行上述任务,修改通用项目,遍历项目,而不考虑形状?
我无法想象的:
collectionView->scene()->addItem(item1);
如果我的项目是多边形类型,Item::paint()
函数 - 以及所有其他相关函数 - 会被调用,还是 PolyItem::paint()
?
我是否必须插入 switch-case 代码来确定和转换每一个实例,每一个类型?
根据运行时决策创建不同的对象是 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*
类型进行操作,不知道也不关心您正在创建的特定类型是什么。它将 return Item*
个实例,因此无论它们的具体实例类型如何,您都可以更改它们的共同属性。
可能值得将您的 Item class 缩减为 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定义的通用接口。每个 child 都为适当的 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());
每个派生的 class 实现由 BaseItem 定义的 接口,而您在通用操作中将所有项目视为 BaseItem。这也使代码易于扩展 - 添加新的项目类型只需要实现派生 class,但现有代码保持原样 - 因此需要仔细设计 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。我无法想象事情应该如何运作。
当前实施:
class Item : public QGraphicsItem
{
public:
typedef enum { PolygonType = QGraphicsItem::UserType + 1 } ShapeType;
Item() {...}
Item(const Item ©Item) // 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;
....
}
}
根据选择,我创建了一个项目并添加到 canvas...然后在完成所有更改后,我将其添加到 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))));
...
}
我想要达到的目标
这是一个最小的方法,但我必须将功能扩展到扩展其他 classes 的项目,例如 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 来修改其他属性,比如颜色?)
-----> 我正在考虑制作一个不同的构造函数来创建正确的形状,在 class Item - 但这似乎意味着 Item 需要 class 多边形需要 class 项目...循环依赖?
- 更复杂的是,我如何执行上述任务,修改通用项目,遍历项目,而不考虑形状?
我无法想象的:
collectionView->scene()->addItem(item1);
如果我的项目是多边形类型,Item::paint()
函数 - 以及所有其他相关函数 - 会被调用,还是 PolyItem::paint()
?
我是否必须插入 switch-case 代码来确定和转换每一个实例,每一个类型?
根据运行时决策创建不同的对象是 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*
类型进行操作,不知道也不关心您正在创建的特定类型是什么。它将 return Item*
个实例,因此无论它们的具体实例类型如何,您都可以更改它们的共同属性。
可能值得将您的 Item class 缩减为 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定义的通用接口。每个 child 都为适当的 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());
每个派生的 class 实现由 BaseItem 定义的 接口,而您在通用操作中将所有项目视为 BaseItem。这也使代码易于扩展 - 添加新的项目类型只需要实现派生 class,但现有代码保持原样 - 因此需要仔细设计 BaseItem 以提供所有项目通用操作的接口。
class Item : public BaseItem, public QGraphicsItem {
void AddToScene(QGraphicsScene* scene) override { scene->addItem(this); }
// Caller takes ownership.
BaseItem* CreateCopy() override { return new Item; }
};