在这种情况下,我应该/如何避免垂头丧气?

时间:2019-12-17 00:02:47

标签: c++ oop polymorphism downcast

说我有一个基类和派生类,其中派生类实现了一些其他制造特定的功能:

class Device {
// Base class
}

class DeviceFromSpecificManufacture : public Device {
// Derived
}

我的程序运行时,它要求用户从一系列可用设备中选择一个设备。在这一点上,使用基类对我来说很好,因为我只需要基本的设备功能(与制造商无关)

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

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

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

执行此操作的一种方法是,在程序需要使用特定功能的时候,将我的基本实例转换为派生类型。

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.

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

3 个答案:

答案 0 :(得分:4)

垂头丧气几乎总是设计中矛盾的征兆。您的矛盾就在这里:

  

[…]我可以使用基类,因为我只需要基本的设备功能[…]

     

[…]我将只需要使用实现制造特定功能的类。

显然,仅了解基类是不合适的,因为突然之间 事实证明您要做必须了解更具体的类型!?

通过使一段代码带有一个Device,您可以表示:这段代码可以与任何类型的Device一起使用。如果这段代码然后必须向下转换给出的Device并检查它是否实际上可以处理的Device类型,那么我们必须问自己一个问题:如果这段代码的代码实际上无法与任何类型的Device一起使用,为什么它接受任何类型的Device作为输入?如果给此代码Device无法使用该怎么办?在实现过程中必须向下转换的组件说了一件事,然后又做了另一件事……建议阅读:Liskov substitution principle

问题在于哪种设计可以在您的特定应用程序中工作取决于特定的应用程序。在不了解该应用程序的情况下,很难提出什么是修复设计的好方法。但是,这里有一些想法:

为什么将所有设备存储在同一集合中?为什么不将设备分别存储在单独的集合中?这样一来,您不仅可以向用户显示设备,还可以按类别显示设备。这也意味着您不会丢弃所需的信息。

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

最后,每当您看到std::shared_ptr时,都要问自己:这个对象确实有多个所有者吗?实际的共享所有权方案应该很少。就您而言,您似乎将设备存储在容器中。可能是,包含该容器的所有内容都是这些设备的唯一所有者。因此,std::unique_ptr可能是一个更合适的选择……

答案 1 :(得分:0)

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

答案 2 :(得分:0)

通常,此方法是使基类具有该功能的默认实现,该功能完全不执行任何操作。然后仅在需要执行某些操作的派生类中重写它。如:

def main():
    the_code_you_want_to_start
    ...


if __name__ == "__main__":
    main()

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

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
    }
}

但是那部分也可能是不必要的过度杀伤力。这取决于您的真正需求。