наследование системы частиц c ++ - PullRequest
0 голосов
/ 03 мая 2020

Я создаю систему частиц, и я хочу иметь возможность выбрать, какой тип объекта будет отображаться на экране (например, просто пиксели или круговые формы). У меня есть один класс, в котором хранятся все параметры (ParticleSettings), но без тех объектов, которые хранят точки или формы окружностей и т. Д. c. Я думал, что я могу создать чистый виртуальный класс (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; }
...
}

Итак ... Я знаю, что у меня нет доступа к методу getParticleVertex () с помощью полиморфизма. И я действительно хочу иметь такой доступ. Я хочу спросить, есть ли лучшее решение для этого. У меня действительно плохие времена, когда я решаю, как все это соединить. Я имею в виду, что я тоже думал об использовании шаблонных классов, но мне нужно, чтобы это было динамически c связывание, а не stati c. Я думал, что эта идея полиморфизма будет в порядке, но мне действительно нужно иметь доступ к этому методу в этой опции. Не могли бы вы помочь мне, как это должно быть сделано? Я хочу знать, каков наилучший подход здесь, а также, если у меня есть какой-то хороший ответ на эту проблему, если я решу сделать так, как я покажу вам выше.

1 Ответ

0 голосов
/ 03 мая 2020

Судя по его звукам, абстрактный класс ParticlesInterface не просто имеет виртуальный getParticleVertex, потому что это вообще не имеет смысла, только для указанного c типа 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 выше также не расширяется, если когда-либо понадобится больше типов. Даже если есть универсальный c 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, который не заботится об этом обратном вызове, не должен переопределять его.

Старайтесь сохранять виртуальные функции интерфейса простыми в намерение, следуя принципу единой ответственности. Если вы не можете написать в предложении или двух, какова цель или ожидаемое поведение функции в целом, это может быть слишком сложно, и, возможно, его можно было бы легче представить в небольших шагах. Или, если вы обнаружите, что виртуальные переопределения в нескольких классах имеют сходные шаблоны, возможно, некоторые меньшие части в этих реализациях могут быть значимыми виртуальными функциями; и более крупная функция может или не может оставаться виртуальной, в зависимости от того, можно ли считать то, что остается, действительно универсальным для интерфейса.

(Лучшие практики программирования - это советы, подкрепленные вескими причинами, но не абсолютные l aws Я не собираюсь говорить «НИКОГДА не использовать dynamic_cast». Иногда по разным причинам может иметь смысл нарушать правила .)

...