类设计以避免需要基类列表
Class design to avoid need for list of base classes
我目前处于一个类库的设计阶段,偶然发现了一个类似于"使用没有RTTI的中央管理器管理不同的类"或"避免dynamic_cast的模式"的问题。
假设有一个类层次结构,它有一个基类 base 和两个类DerivedA和DerivedB,它们都是 base 的子类。在我的库的某个地方将有一个类,需要持有两种类型的对象列表DerivedA和DerivedB。进一步假设该类需要根据类型对两种类型执行操作。显然,我将在这里使用虚函数来实现这种行为。但是,如果我需要管理类为我提供类型为DerivedA的所有对象,该怎么办?
这是一个糟糕的类设计的指标,因为我有需要执行操作,只对类层次结构的一个子集?
或者它只是意味着我的管理类不应该使用Base的列表,而是两个列表-一个用于DerivedA和一个用于DerivedB?因此,如果我需要对两种类型执行一个操作,我就必须遍历两个列表。在我的情况下,需要向层次结构添加新子类的可能性非常低,目前的数量大约是3或4个子类。但是如果我需要管理类给我的所有对象类型DerivedA ?
这是一个糟糕的类设计的指示,因为我有需要仅对类层次结构的子集执行操作?
是的可能性大于否。如果您经常需要这样做,那么质疑层次结构是否有意义是有意义的。在这种情况下,你应该把它分成两个不相关的列表。
另一种可能的方法是通过虚拟方法来处理它,例如DeriveB
将有一个不影响该操作的方法的无操作实现。
如果你把(指针指向的)对象存储在一起,而必须以不同的方式处理,这肯定是一个糟糕的设计的标志。
但是,您可以在基类中作为空函数实现这种不同的行为,或者使用访问者模式。
您可以通过几种方式来做到这一点。
- 尝试
dynamic_cast
到特定的类(这是一个暴力解决方案,但我只会使用它的接口,使用它的类是一种代码气味。它会工作的。) -
这样做:
class BaseRequest {}; class DerivedASupportedRequest : public BaseRequest {};
然后修改你的类来支持这个方法:
// (...) void ProcessRequest(const BaseRequest & request);
-
在基类中创建虚方法
bool TryDoSth()
;DerivedB
将始终返回false,而DerivedA
将实现所需的功能。 - 替代上述:创建方法
Supports(Action action)
,其中Action
是一个枚举,定义可能的操作或操作组;在这种情况下,调用DoSth()
类,它不支持给定的特性应该导致抛出异常。 - 基类可以有一个方法
ActionXController * GetControllerForX()
;DerivedA
将返回实际控制器,DerivedB
将返回nullptr
。 类似地,基类可以提供方法:
BaseController * GetController(Action a)
你问,这是不是一个糟糕的设计。我认为,这取决于多少功能是共同的,多少是不同的。如果您有100种常用方法,而只有一种不同的方法,那么将这些数据保存在单独的列表中会很奇怪。但是,如果注意到不同方法的数量,请考虑更改应用程序的设计。这可能是一个普遍的规则,但也有例外。不知道上下文很难判断
- 为什么我需要在成员发起器列表中重复基类的模板参数?
- 在初始化列表之外手动调用基类的构造函数
- 虚拟基类的派生类列表
- 为什么 C++ 的创建者决定使用构造函数初始值设定项列表来初始化基类?
- 子类对象列表重新解释为基类对象列表?(C++11).
- 将initalizer列表与从空基类继承的结构一起使用
- 使用不包括基类的模板从C++中的列表中查找特定类型
- 流运算符和多态基类列表
- 为什么在派生最多的类的初始值设定项列表中显式调用虚拟基类构造函数的规则,而较老的祖先已经拥有它?
- 从其基类列表中调用重写的函数
- 是否可以在基类初始化器列表中传递成员对象
- 初始值设定项列表:基类和成员函数中的构造函数
- 自动将派生类的指针列表转换为基类的指针列表
- 如何制作一个既可以包含基类又可以包含派生类的列表
- 在模板基类列表中使用类本地类型别名
- 当后代需要不同的参数列表时,我应该如何在基类中定义抽象方法
- C++基类列表以及如何确定类类型
- Boost.Python:来自 python 中C++对象:无法添加到基类列表中C++
- 用于可变模板的基类列表
- 类设计以避免需要基类列表