我正在创建粒子系统,我希望有可能选择屏幕上将显示哪种对象(例如像素或圆形)。我有一类存储所有参数的类(ParticleSettings),但是没有那些存储点或圆形状等的实体。我认为我可以创建纯虚拟类(ParticlesInterface)作为基类,其派生类如用于存储那些可绘制对象的ParticlesVertex或ParticlesCircles。就像这样:
class ParticlesInterface
{
protected:
std::vector<ParticleSettings> m_particleAttributes;
public:
ParticlesInterface(long int amount = 100, sf::Vector2f position = { 0.0,0.0 });
const std::vector<ParticleSettings>& getParticleAttributes() { return m_particleAttributes; }
...
}
和:
class ParticlesVertex : public ParticlesInterface
{
private:
std::vector<sf::Vertex> m_particleVertex;
public:
ParticlesVertex(long int amount = 100, sf::Vector2f position = { 0.0,0.0 });
std::vector<sf::Vertex>& getParticleVertex() { return m_particleVertex; }
...
}
所以...我知道我无法使用polimorphism访问getParticleVertex()方法。我真的很想拥有这种访问权限。我想问一下是否有更好的解决方案。我真的很难决定如何将所有这些连接在一起。我的意思是我也在考虑使用模板类,但我需要它是动态绑定而不是静态绑定。我认为这种多态性的想法可以,但是我真的需要在该选项中使用该方法。您能帮我怎么做吗?我想知道什么是最好的方法,并且如果我决定以这种方式在上面向您展示,那么对于这个问题是否有什么好的答案。
从它的声音来看,ParticlesInterface
抽象类不只是具有虚拟的getParticleVertex
,因为一般而言,这仅对特定类型ParticlesVertex
或一组相关类型而言没有意义” 。
这里推荐的方法是:每当您需要根据实际的具体类型执行不同操作的代码时,请将那些“不同的东西”设置为界面中的虚拟函数。
所以从以下开始:
void GraphicsDriver::drawUpdate(ParticlesInterface &particles) { if (auto* vparticles = dynamic_cast<ParticlesVertex*>(&particles)) { for (sf::Vertex v : vparticles->getParticleVertex()) { draw_one_vertex(v, getCanvas()); } } else if (auto* cparticles = dynamic_cast<ParticlesCircle*>(&particles)) { for (CircleWidget& c : cparticles->getParticleCircles()) { draw_one_circle(c, getCanvas()); } } // else ... ? }
([
CircleWidget
组成。我对sf
不熟悉,但这不是重点。)
由于getParticleVertex
并非对每种ParticleInterface
都有意义,因此从接口使用该代码的任何代码都必须具有某种类似于if
的检查,以及一个dynamic_cast
以获取实际的数据。如果需要更多类型,则上面的drawUpdate
也是不可扩展的。即使有一个通用的else
可以“处理”其他所有内容,但实际上一种类型需要一些自定义提示,这暗示着其他将来的类型或对现有类型的更改在那一刻也可能需要自己的自定义行为。取而代之的是,从事物代码与接口的关系更改为可以要求接口做的事物:
class ParticlesInterface { // ... public: virtual void drawUpdate(CanvasWidget& canvas) = 0; // ... }; class ParticlesVertex { // ... void drawUpdate(CanvasWidget& canvas) override; // ... }; class ParticlesCircle { // ... void drawUpdate(CanvasWidget& canvas) override; // ... };
现在,粒子类更“活跃”-他们积极地做事,而不是仅仅采取行动。
[例如,假设您发现ParticlesCircle
,但没有找到ParticlesVertex
,则每当坐标更改时,都需要进行一些成员数据更新。您可以在virtual void coordChangeCB() {}
上添加ParticlesInterface
,并在每次运动模型滴答之后或任何时候调用它。使用接口类中的{}
空定义时,任何不关心该回调的类,例如ParticlesVertex
,都不需要覆盖它。
请遵循“单一职责原则”,尽量使界面的虚拟功能保持简单。如果您不能用一两句话写出该函数的总体目的或预期行为,那么它可能太复杂了,也许更容易在较小的步骤中加以考虑。或者,如果您发现多个类中的虚拟覆盖具有相似的模式,则这些实现中的一些较小部分可能是有意义的虚函数;而较大的功能可能保持虚拟状态,也可能不保持虚拟状态,具体取决于剩余的内容对于该接口是否真正通用。
((编程的最佳实践是建议,有充分的理由支持,但不是绝对的定律:我不会说“从不使用dynamic_cast
”。出于种种原因,有时break the rules才有意义。)