使用 "irrelevant" 方法的继承 class 设计

Inheritance class design with "irrelevant" methods

假设我有以下抽象基础 class:

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:

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 函数,因此必须 overridden/implemented 在 DLA_2dDLA_3d 子 class 中,3D 方法在 DLA_2d 中什么都不做,反之亦然 DLA_3d.

当然,这行得通,一切正常,但我不禁觉得设计有点笨拙,因为必须在每个 class.

中实现不相关的方法

是否有更好的设计模式,例如为两个派生的 classes 实现单独的接口(即 ISpawnParticle_2dISpawnParticle_3d)?还是在这种情况下更倾向于组合而不是继承?

编辑: 我应该补充一点 DLAContainer 还有其他几种方法(和字段)。其中一些方法已定义(因此 DLA_2dDLA_3d 都可以使用它们),其他方法是纯虚拟的,类似于 spawn_particle - 这就是为什么我有 DLAContainer 在这种情况下作为抽象基础 class。

你这里的根本问题是抽象基class声明的接口不一致。这应该是 2D 还是 3D 界面?你声明它的方式,它是两者,但派生的 classes 是两者之一,因此没有完全实现接口:这会导致你所有的问题。

想一想你打算如何使用基数class,也许你可以把它做成一个超过维度数的模板?如果有更多 virtual 独立于维度的方法,它们可以在一个基础中,例如

struct DLAContainerBase
{
  /* some basic virtual interface */
};

template<int Dims>
struct DLAContainer : DLAContainerBase
{
  virtual void spawn_particle(std::array<int,Dims>&,
                              std::uniform_real_distribution<>&) = 0;
};

但是w/o知道基地将如何使用,我不能给你可靠的建议。顺便说一句,您还可以为纯虚方法(例如抛出的方法)提供实现。

你是对的,它很笨拙。
这是 OO 设计中一个常见错误的结果:当子类型不能说是 IS A 父类型时,仅使用继承来避免代码重复。

目前您可以拨打:

DLA_3d d3;
d3.spawn_particle(...) //The 2D version
//and
DLA_2d d2;
d2.spawn_particle(...) //The 3D version

似乎没有 "ill effects" 忽略调用并且什么也不做。问题是调用 spawn_particle 的代码需要注意:

  • 调用方法可能什么都不做。
  • 或者预先检查类型以了解调用哪个方法。

这两个都对调用者施加了不必要的额外 knowledge/work。并有效地使其更容易出错。

PS:请注意,在运行时抛出错误并不能真正修复设计。因为调用者现在只剩下:“调用该方法可能会抛出 或预先检查类型...”


您可以通过多种方式改进您的设计。但最终你知道你想要实现什么,并且必须自己做出决定。
这里有一些想法:

  • 首先要考虑以下设计原则:组合优先于继承
    • 您无需继承即可重用代码:您可以 contain/reference 另一个对象的实例,并通过调用 contained/referenced 对象来卸载工作。
    • 您提到 DLAContainer 还有许多其他字段和方法。其中有多少可以移动到不同的或多个 类?
  • 容器生成粒子真的有意义吗?容器的职责应该是盛东西。您是否遵循 单一职责原则? (我对此表示怀疑。)
  • 考虑将每个 spawn_particle 方法移动到适当的子类。 (虽然我怀疑这会给你留下非常相似的问题。)
  • 为 "particle" 开发抽象。然后 "particle spawners" 可以具有相同的签名,但会生成 "particle" 的不同具体实例,即2D 粒子或 3D 粒子。