Re: Help me find the best class design for following problem
"Peter" <Will_Bounce_So_Use_My_First_Name@Smart-Projects.net> wrote in
news:ro7tq.10334$D86.4403@newsfe21.ams2:
Hi,
I have following situation that I'm trying to find the best class
design for:
Base class: Device
Two classes inherit from this Device class, let's call them DeviceA
and DeviceB.
Both Device classes A and B can also be emulated via a file on the HD
instead of actually talking to the device.
However these files come in many flavours. Many are unique to the
type of device, yet some are possible for the two types of devices.
So in one case file X is for device A, but in another it's for device
B
So I was first thinking of a template where I inherit from the
template class
template <class T>
class FileDevice : public T
And in this class I put the huge shared code base of the two types
possible
Then I create file-type specific classes that inherit from either
FileDevice<DeviceA> or FileDevice<DeviceB>
And for the file type classes that cover both devices I probably best
also make them similar templates and instanciate objects either using
FileDevice<DeviceA> or FileDevice<DeviceB> as base class.
How do you feel about this ?
But then I run into following issues:
In the GUI part of the code there are several places where it makes
sense to see if I'm using a file or an actual device, for display and
other user interface purposes (e.g. loading/unloading a file etc.).
There I would normally test the Device pointer via a dynamic cast. If
(dynamic_cast<FileDevice*>(DevicePtr)) { do stuff ; }
However with a template design this doesn't work, as FileDevice needs
a template class.
Fact of the matter is that both DeviceA and DeviceB are Device classes
but testing this: If (dynamic_cast<FileDevice<Device>*>(DevicePtr)) {
do stuff ; }
won't work I assume ?
So how would you do this ?
So I was also thinking of multiple inheritance. Something I've never
done in my code but anyway:
class FileAccess {}
template <class T>
class FileDevice : public T, FileAccess
Doing this I would be able to test:
If (dynamic_cast<FileAccess*>(DevicePtr)) { do stuff ; }
*I assume* ?
However there are other problems then as well, since FileAcces is NOT
A Device class I can't pass this pointer to functions that expect a
Device pointer, while in fact the FileAccess device in use IS in
reality a Device through inheritance.
I suppose a dynamic_cast will help here ? But I'm not sure.
Would this work:
FileAccess *FileDev = new FileTypeXDevice() ; // FileTypeXDevice
inherits from FileAccess AND Device, DeviceA etc.
Function(FileDev) ; // Where the function takes a Device pointer
(Function (Device *MyDevice) // This won't work
Function(dynamic_cast<Device*>(FileDev)) ; // this would compile but
would a the pointer be passed as a Device pointer /
Dynamic_cast should be ever needed only if you don't have full control
over the whole codebase and have to resort to this kind of hacking to
glue the pieces together.
If you have full control, then in an ideal design there should be no need
to find out anything about the objects actual type. Instead one calls the
object's virtual functions which do the right thing automagically.
In a bit less ideal world you would have a function IsDevice() or
something like that which would return true or false appropriately. This
may be a virtual function or even an ordinary function reporting the
state of a bit flag in the base class. It may even rely on a couple of
dynamic_casts, this is not so important as far as it works correctly and
is simple to use from outside.
I'm not fully convinced that you are not over-engineering this. Look at
the places where you are using these classes. Do they expect Device
interface? DeviceA? DeviceB? If a DeviceA interface is required, and this
interface can be provided by both a real device and an emulator, then it
means DeviceA should be just a pure interface and both the real device
and the emulator should be derived from DeviceA (never instantiate non-
leaf classes!) If there is some common code in the emulator for devices A
and B then you can put this in separate functions (yes, C++ has
standalone functions!) or use private (multiple) derivation from some
implementation class if there is a lot of data state to be maintained.
hth
Paavo