使用 "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_2d
和 DLA_3d
子 class 中,3D 方法在 DLA_2d
中什么都不做,反之亦然 DLA_3d
.
当然,这行得通,一切正常,但我不禁觉得设计有点笨拙,因为必须在每个 class.
中实现不相关的方法
是否有更好的设计模式,例如为两个派生的 classes 实现单独的接口(即 ISpawnParticle_2d
和 ISpawnParticle_3d
)?还是在这种情况下更倾向于组合而不是继承?
编辑: 我应该补充一点 DLAContainer
还有其他几种方法(和字段)。其中一些方法已定义(因此 DLA_2d
和 DLA_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 粒子。
假设我有以下抽象基础 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_2d
和 DLA_3d
子 class 中,3D 方法在 DLA_2d
中什么都不做,反之亦然 DLA_3d
.
当然,这行得通,一切正常,但我不禁觉得设计有点笨拙,因为必须在每个 class.
中实现不相关的方法是否有更好的设计模式,例如为两个派生的 classes 实现单独的接口(即 ISpawnParticle_2d
和 ISpawnParticle_3d
)?还是在这种情况下更倾向于组合而不是继承?
编辑: 我应该补充一点 DLAContainer
还有其他几种方法(和字段)。其中一些方法已定义(因此 DLA_2d
和 DLA_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 粒子。