Re^2: Understanding 'Multiple Inheritance'
by Anonymous Monk on Mar 07, 2005 at 11:40 UTC
|
If objects in Perl6 will be as the apocalypse says (and other than minor changes, I've no reason to believe it will not), objects in general, and MI more specifically will be dramatically better in perl6 than in perl5.
I've never been real tempted by MI
I've always found the need to use MI very natural - perhaps partially because in the first OO environment I programmed in, MI was used a lot, and the language supported it well. One sees MI in every day objects. You have CD-players, and you have radios, but you also have devices that are both. That doesn't mean most of your objects need MI - but it does mean that the lack of MI (either by no language support, by convention, or by being pressured in the believe that MI is bad (or difficult (as one language designer/compiler implementor once said "MI isn't difficult to use - it's difficult to implement well"))) makes it harder design your programs in a natural way, or to truely make use of inheritance (and hence, code reuse).
| [reply] |
|
|
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.
- Implement the MI such that it redispatches any method that returns a 'Not processed' code. If the first (by some defintion) subclass implementing the method returns this value, the method is redispatched to the next subclass that implements it, until one of them responds saying 'Okay, I've got that one'.
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.
- Override the Set/GetVolume() method in the superclass and manually dispatch to the approprate baseclass depending upon a the state of a 'mode' parameter.
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:
- the output flow from base classes to be conditional upon the settings of all of the ancillary controls + the "We are the currently selected device" flag.
- Or, the base classes would need to expose their output methods so that the superclass could overide them to perform the "Selected" test--which creates further problems of close coupling and wider interfaces.
- Or the bases classes would need to provide callbacks for every output method. Again, close-coupling and overhead result.
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.
Examine what is said, not who speaks.
Silence betokens consent.
Love the truth but pardon error.
| [reply] |
|
|
You knew someone was going to reply from the P6 aspect, right? Please tell me you did! :-)
Implement the MI such that it redispatches any method that returns a 'Not processed' code. If the first (by some defintion) subclass implementing the method returns this value, the method is redispatched to the next subclass that implements it, until one of them responds saying 'Okay, I've got that one'.
Coincedentally, that's exactly (well, almost) how Perl6 will implement it. The method dispatcher will ask all the classes involved (in some order you the programmer get to specify) who wants to handle it. Each class can be written with that in mind or they can just handle it and be selfish. But, the mechanism will exist.
The real problem with the CD player / Radio example is a that the problem was ill-conceived. A CD-player / Radio shouldn't be viewed as both is-a a CD-player and a Radio. Those items aren't the true atomic concepts as a CD-player and a Radio are each several items. A better view would be a pipeline of objects that provide for a plugin mechanism. So, you would have
- Output(s)
- MusicMaker(s)
- Control(s)
- PowerSupply(s)
Then, you build it like you have a bunch of Legos, which is how they're built in the real world. And, you do have mode switches for most of those categories which switches the active FOO from A to B. In other words, the actual physical item is built with composition, but the Foobars each inherit from some base Foobar for an interface and some common implementation bits.
Being right, does not endow the right to be rude; politeness costs nothing. Being unknowing, is not the same as being stupid. Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence. Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.
| [reply] |
|
|
|
|
|
|
|
|
|
|
|
|
As DragonChild suggests, you ought to take a more component oriented
approach, assembling the final product from base components.
I would probably have something along the lines of the following
classes: CDPlayerInterface, RadioInterface, AudioDeviceInterface,
CDPlayer, Radio, and RadioCDCombo. The Radio class would be a
marriage of the RadioInterface and AudioDeviceInterface, the CDPlayer
class would be a marriage of the CDPlayerInterface and the
AudioDeviceInterface, and the RadioCDCombo class would be a marriage
of CDPlayerInterface and RadioInterface and AudioDeviceInterface.
Or something like that... I'm not sure I like the specifics of
my solution, but I think the general paradigm is better.
| [reply] |
|
|
|
|
|
|
BrowserUk wrote:
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!".
Funny, the one that I've learned is "Always be neat, even when you think you don't have to."
| [reply] |
|
|
|
|