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