Should/how can I avoid downcasting in this case?

安稳与你 提交于 2020-01-05 05:10:49

问题


Say I have a base and derived class, where the derived class implements some additional manufacture specific functionality:

class Device {
// Base class
}

class DeviceFromSpecificManufacture : public Device {
// Derived
}

When my program runs, it requires the user to select a device from an array of available devices. At this point, it's fine for me to use the base class as I only require basic device functionality (nothing specific to the manufacture):

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

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

The problem is: at some point I'm going to need to only work with the classes that implement manufacture specific functionality.

One way I can do this is by downcasting my base instance to the derived type, when the program is at a point where it needs to use the specific functionality.

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.

Is there a better way to do this? I cannot move the specific functionality to the base class, as it's really not applicable to all devices, just some.


回答1:


A downcast is almost always a symptom of a contradiction in the design. Your contradiction lies here:

[…] 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.

Apparently it's not fine for you to only know about the base class because, suddenly, it turns out you do have to know about the more concrete type!?

By having a piece of code take a Device, you express: This piece of code works with any kind of Device. If this piece of code has to then downcast the Device it was given and check whether it is of the kind of Device it actually can deal with, then we have to ask ourselves the question: If this piece of code cannot actually work with any kind of Device, why did it accept any kind of Device as input? What happens if this code is given a Device it can't work with? A component that has to downcast in its implementation says one thing and does another… recommended reading: Liskov substitution principle.

The problem is that what kind of design would work in your particular application depends on the particular application. Without knowing more about that application, it's very hard to suggest what would be a good way to fix the design. However, here are a few thoughts:

Why store all devices in the same collection? Why not store the devices in separate collections, one for each kind? That enables you to not just display the devices to the user, but display them by category. It also means you do not throw away the information you need.

Alternatively, even if you do not know the concrete types of all objects in your data structure, the objects themselves always know what they are. The Double Dispatch pattern (basically a version of the Visitor pattern) may be of interest to you.

Finally, whenever you see an std::shared_ptr, ask yourself: Does this object really have more than one owner? Actual shared ownership scenarios should be rather rare. In your case, you seem to be storing devices in a container. Chances are that whatever contains that container is the sole owner of these devices. Thus std::unique_ptr would probably be a more appropriate choice…




回答2:


Use the visitor pattern to call the device specific behaviour https://en.m.wikipedia.org/wiki/Visitor_pattern




回答3:


Generally the approach to this would be to make the base class have a default implementation of the function that does nothing at all. Then only override it in derived classes which need to do something. Such as:

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

You can also supplement this with other methods that express other information that whether a derived class is different in some way. Such as:

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

But that part could also be unnecessary overkill. It depends on what you really need.



来源:https://stackoverflow.com/questions/59365859/should-how-can-i-avoid-downcasting-in-this-case

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!