假设我有以下抽象基类:
class DLAContainer {
public:
DLAContainer() { std::random_device rd; mt_eng = std::mt19937(rd()); }
virtual void generate(std::size_t _n) = 0;
protected:
std::mt19937 mt_eng;
virtual void spawn_particle(int& _x, int& _y,
std::uniform_real_distribution<>& _dist) = 0;
virtual void spawn_particle(int& _x, int& _y, int& _z,
std::uniform_real_distribution<>& _dist) = 0;
// ... among other methods to be overridden...
};
和继承自 DLAContainer
的两个类:
class DLA_2d : public DLAContainer {
public:
DLA_2d() : DLAContainer() { // initialise stuff }
void generate(std::size_t _n) { // do stuff }
private:;
std::queue<std::pair<int,int>> batch_queue;
// ...
void spawn_particle(int& _x, int& _y,
std::uniform_real_distribution<>& _dist) { // do stuff }
void spawn_particle(int& _x, int& _y, int& _z,
std::uniform_real_distribution<>& _dist) { // do nothing }
//...
};
和
class DLA_3d : public DLAContainer {
public:
DLA_3d() : DLAContainer() { // initialise stuff }
void generate(std::size_t _n) { // do stuff }
private:;
std::queue<std::tuple<int,int,int>> batch_queue;
// ...
void spawn_particle(int& _x, int& _y,
std::uniform_real_distribution<>& _dist) { // do nothing }
void spawn_particle(int& _x, int& _y, int& _z,
std::uniform_real_distribution<>& _dist) { // do stuff }
//...
};
如您所见,spawn_particle
有两个重载 - 一个用于 2D 晶格,另一个用于 3D,但是两者都是纯 virtual
函数,因此必须在 DLA_2d
和 DLA_3d
子类中覆盖/实现,其中 3D 方法在 DLA_2d
中不执行任何操作,对于 DLA_3d 反之亦然
。
当然,这行得通,一切正常,但我不禁觉得设计有点笨拙,因为必须在每个类中实现不相关的方法。
是否有更好的设计模式,例如为两个派生类实现单独的接口(interface)(即 ISpawnParticle_2d
和 ISpawnParticle_3d
)?还是在这种情况下更倾向于组合而不是继承?
编辑:我应该补充一点,DLAContainer
还有其他几种方法(和字段)。其中一些方法已定义(这样它们就可以被 DLA_2d
和 DLA_3d
使用),其他方法是纯虚拟的,类似于 spawn_particle
-这就是为什么在这种情况下我将 DLAContainer
作为抽象基类。
最佳答案
你是对的,它很笨拙。
这是 OO 设计中一个常见错误的结果:当子类型不能说是 IS A 父类型时,使用继承仅仅是为了避免代码重复。
目前您可以调用:
DLA_3d d3;
d3.spawn_particle(...) //The 2D version
//and
DLA_2d d2;
d2.spawn_particle(...) //The 3D version
似乎没有通过忽略调用和不执行任何操作而产生的“不良影响”。问题是调用 spawn_particle
的代码需要注意:
- 调用该方法可能什么都不做。
- 或者预先检查类型以了解调用哪个方法。
这两者都对调用者施加了不必要的额外知识/工作。并有效地使其更容易出错。
PS:请注意,在运行时抛出错误并不能真正修复设计。因为调用者现在只剩下:“调用该方法可能会抛出或预先检查类型...”
您可以通过多种方式改进您的设计。但最终您知道自己想要实现的目标,并且必须自己做出决定。
这里有一些想法:
- 首先要考虑以下设计原则:优先组合而不是继承。
- 您无需继承即可重用代码:您可以包含/引用另一个对象的实例,并通过调用包含/引用的对象来卸载工作。
- 您提到
DLAContainer
有许多其他字段和方法。其中有多少可以转移到不同的或多个类(class)?
- 容器生成粒子真的有意义吗?容器的职责应该是盛东西。您是否遵循单一职责原则? (我对此表示怀疑。)
- 考虑将每个
spawn_particle
方法移动到适当的子类。 (虽然我怀疑这会给你留下非常相似的问题。) - 开发“粒子”的抽象概念。然后两个“粒子生成器”可以具有相同的签名,但生成不同的“粒子”具体实例,即2D 粒子或 3D 粒子。
关于c++ - 使用 "irrelevant"方法的继承类设计,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37769502/