You have CD-players, and you have radios, but you also have devices that are both.
One of the problems with MI arises when elements common to both (all) the parent objects collide.
In your example, both a radio and a CD player need a volume control, but the combined device does not need two. Using MI to build a CDRadio class from a CD class and a Radio class results in a conflict for methods like Set/GetVolume().
There are two ways to resolve this using MI.
Now you have the problem that every class that will ever be subclassed has to retain state to know that it is currently selected. And every method of every class that can be inherited has to first check to see if it is currently selected and return the 'Not processed' message if it is not.
That creates a huge burden on the designers and implementors of inheritable classes to provide that state and the checks.
It also creates a runtime cost in performing those checks.
You would need to do this for every common method.
This is exactly what you would do in a composite class and throws away any benefit from MI.
And that is only one side of the problem--the inbound flow. Using your example, both sub-devices would also share a common set of speakers. The flow from the sub-devices to the speakers is initiated, probably asynchronously, from with the subdevice itself as a result of many inbound control flows: volume controls; tone controls; balance controls; mute etc.
In order to ensure that the speakers only receive output from the currently selected device, the MI model would require:
With the composite model, the "select device" function is entirely encapsulated within the superclass and the base classes need never consider the possibility of their not being the 'top of the heap' class, structure-wise. The super class simply dispatches to the selected sub-device depending upon the state of the "selected device" flag and everything works. The non-selected device doesn't have to consider whether it is the selected device, because if it isn't, it never gets called.
In Perl-OO terms, you could even arrange that re-dispatch to the appropriate sub-device be (for any non-overiden methods) done through an AUTOLOAD routine that simple re-blesses the object into the currently selected object and calls it (undoing the rebless on the way back out).
And that's the crux of the problem with MI. There is no way to know what uses a class will be put to when writing it, or what other base classes it might be called upon to coexist with, and trying to write every class such that it can co-exist with any other siblings is costly in both design and implementation time and in runtime overhead.
By deferring those considerations to only those projects where they are needed, through composition rather than MI, you simplify the design and implementation of the bases classes and the co-existance decisions. You will then know exactly what decisions need to be made, because you will know what the 'other class(es)' are.
In the above example, the "selected device state" only needs to be stored and known within the superclass, and not all the bases classes also.
The single biggest (and hardest) lesson I've had to learn through experience, because it was never covered on any CS course I took--nor any I have read about since--is: "Don't do it unless you have to!".
Deferring a difficult decision until you have the information to make the it based upon the exact circumstances in which it needs to be made, saves a huge amount of speculation about what might be true when that time arrives.
Using composition rather than MI makes that process natural and intuative.
In reply to Re^3: Understanding 'Multiple Inheritance'
by BrowserUk
in thread Understanding 'Multiple Inheritance'
by punkish
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |