Should/how 在这种情况下我可以避免向下转型吗?

Should/how can I avoid downcasting in this case?

假设我有一个基础和派生的 class,其中派生的 class 实现了一些额外的特定于制造商的功能:

class Device {
// Base class
}

class DeviceFromSpecificManufacture : public Device {
// Derived
}

当我的程序运行时,它要求用户select 从一组可用设备中选择一个设备。在这一点上,我可以使用基础 class 因为我只需要基本的设备功能(没有特定于制造商):

std::vector<std::shared_ptr<Device>> availableDevices = getAvailableDevices();

// User selects device here, resulting in:
std::shared_ptr<Device> selectedDevice = ...

问题是:在某些时候,我将只需要使用实现制造特定功能的 classes。

我可以做到这一点的一种方法是,当程序处于需要使用特定功能的位置时,将我的基实例向下转换为派生类型。

std::shared_ptr<DeviceFromSpecificManufacture> specificDevice = std::dynamic_pointer_cast<DeviceFromSpecificManufacture>(selectedDevice);

// Here I would need to confirm that the cast was successful (as there's no guarantee 
// that selectedDevice is an instance of DeviceFromSpecificManufacture) - which 
// makes this feel even more wrong.

有更好的方法吗?我无法将特定功能移至基础 class,因为它实际上并不适用于所有设备,仅适用于部分设备。

使用访问者模式调用设备特定行为https://en.m.wikipedia.org/wiki/Visitor_pattern

一般来说,解决这个问题的方法是让基础 class 有一个什么都不做的函数的默认实现。然后只在需要做某事的派生 classes 中覆盖它。如:

class Device {
    virtual void PerformSpecialFunctionality {
        // Base implementation does nothing at all
    }
}

class DeviceA {
    // Does not override PerformSpecialFunctionality becuase it doesn't need to do anything special
}

class DeviceB : public Device {
    void PerformSpecialFunctionality override {
        // Does something specific to only DeviceB
    }
}

您还可以使用其他方法来补充这一点,这些方法表达其他信息,即派生的 class 是否在某些方面有所不同。如:

virtual bool HasSpecialFunctionality {
    return false; // Only some derived classes override this to return true.
}

但这部分也可能是不必要的矫枉过正。这取决于你真正需要什么。

情绪低落几乎总是设计中存在矛盾的征兆。你的矛盾就在这里:

[…] it's fine for me to use the base class as I only require basic device functionality […]

[…] I'm going to need to only work with the classes that implement manufacture specific functionality.

显然,你只知道基数 class 并不好,因为,突然间,它 原来你确实需要了解更具体的类型!?

通过让一段代码采用 Device,您表示:这段代码适用于任何类型的 Device。如果这段代码不得不向下转换给定的 Device 并检查它是否属于它实际可以处理的 Device 类型,那么我们必须问自己一个问题:如果这段代码代码实际上不能与任何类型的 Device 一起使用,为什么它接受任何类型的 Device 作为输入?如果给此代码一个无法使用的 Device 会怎样?一个在实现中不得不向下转型的组件说一套做一套……推荐阅读:Liskov substitution principle.

问题是哪种设计适用于您的特定应用程序取决于特定的应用程序。在不了解该应用程序的更多信息的情况下,很难提出修复设计的好方法。但是,这里有一些想法:

为什么要将所有设备存储在同一个集合中?为什么不将这些设备存放在单独的集合中,每种一个?这使您不仅可以向用户显示设备,还可以按类别显示它们。这也意味着您不会丢弃所需的信息。

或者,即使您不知道数据结构中所有对象的具体类型,对象本身也总是知道它们是什么。 Double Dispatch pattern(基本上是访问者模式的一个版本)您可能会感兴趣。

最后,每当您看到 std::shared_ptr 时,问问自己:这个对象真的有多个所有者吗?实际的共享所有权场景应该很少见。在您的情况下,您似乎将设备存储在容器中。很可能包含该容器的任何东西都是这些设备的唯一所有者。因此 std::unique_ptr 可能是更合适的选择……