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
可能是更合适的选择……
假设我有一个基础和派生的 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
可能是更合适的选择……