类设计以避免需要基类列表

Class design to avoid need for list of base classes

本文关键字:基类 列表      更新时间:2023-10-16

我目前处于一个类库的设计阶段,偶然发现了一个类似于"使用没有RTTI的中央管理器管理不同的类"或"避免dynamic_cast的模式"的问题。

假设有一个类层次结构,它有一个基类 base 和两个类DerivedADerivedB,它们都是 base 的子类。在我的库的某个地方将有一个类,需要持有两种类型的对象列表DerivedADerivedB。进一步假设该类需要根据类型对两种类型执行操作。显然,我将在这里使用虚函数来实现这种行为。但是,如果我需要管理类为我提供类型为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种常用方法,而只有一种不同的方法,那么将这些数据保存在单独的列表中会很奇怪。但是,如果注意到不同方法的数量,请考虑更改应用程序的设计。这可能是一个普遍的规则,但也有例外。不知道上下文很难判断