Re: Understanding 'Multiple Inheritance'
by Zaxo (Archbishop) on Mar 07, 2005 at 05:32 UTC
|
Careful, B is an actual core module.
The OO competition for your subclass is a class which has-a instance of B and has-a instance of C as data members. It is fairly uncommon for multiple inheritance to make more sense than that kind of aggregation.
It may help decide if you reflect on whether A is-a B and A is-a C. Compare that to your answers to the same question with has-a substituted.
Because you justify the A class as a sort of opaque wrapper to keep users out of B and C, I suspect that aggregation will win. Your justification doesn't support a logical descent from B and C. An aggregate constructor based on your requirements will look something like this:
# omit @ISA dance
sub new {
my $class = shift;
bless {
Bobj => B->new(@_[0,1]),
Cobj => C->new(@_[2,3])
}, $class;
}
Again, don't really use 'B' as a package name, it's taken and will get very confusing.
| [reply] [d/l] |
|
|
Careful, B is an actual core module.
Yes, I am am aware of that. Am just using A, B, C... as placeholders (too many Algebra lessons for my daughter).
It may help decide if you reflect on whether A is-a B and A is-a C. Compare that to your answers to the same question with has-a substituted.
Nice. Put that way, A has-a B and has-a C. Thanks for your aggregation code. I will work with that tact.
That said, could I do something like...
package A;
use B;
use C;
use strict;
sub new {
my ($class) = @_;
bless {}, $class;
}
sub make_B {
my ($self) = shift;
return new B(@_);
}
sub make_C {
my ($self) = shift;
return new C(@_);
}
### and then, in my script...
use A;
my $a = A->new();
# $a has all the methods of A
my $b = $a->make_B('a', 'b');
# now $b has all the methods of B
my $c = $c->make_C('c', 'd');
# and $c has all the methods of C
--
when small people start casting long shadows, it is time to go to bed
| [reply] [d/l] |
Re: Understanding 'Multiple Inheritance'
by tilly (Archbishop) on Mar 07, 2005 at 07:40 UTC
|
3 has been answered, so let me answer 1 and 2.
The answer to 1 is that you can't do this. To use multiple inheritance you need to have all classes involved cooperating successfully. If two different classes are being inherited from, and they have unrelated constructors, then you're not going to be able to construct something that is both at once.
The answer to 2 is that even when you can do this, multiple inheritance introduces a lot of complexities that can cause problems. Many people (me for instance) deliberately avoid using multiple inheritance. Others use multiple inheritance, but would generally agree that successfully doing so requires more skill than single inheritance. (And, BTW, single inheritance is often overused as well. The heart of the big benefits from OO is encapsulation, not inheritance.) | [reply] |
|
|
The heart of the big benefits from OO is encapsulation, not inheritance.
The heart of the big benefits from OO is polymorphism. Polymorphism encourages encapsulation, but absolutely requires inheritance.
Polymorphism allows you to derive (inherit) from a class and change its behaviour for certain methods. And that is the basic advantage of OO.
I can do encapsulation in non-OO programming. For example, I can create a private package-level cache of data, create random keys, and require that key to access the data for that "object" (abstract data type), rather than passing around a hash ref. I've created encapsulation.
What I can't do without OO is polymorphism. I cannot create a new ADT that has all of the functionality of the old ADT, except overriding some function to extend it. That requires inheritance.
As to MI - I don't avoid it. I embrace it as much as possible. The only thing is, that I've only found 1 time in about 10 years of OO programming where MI was the right answer. And that was 9 years ago. It's enough to convince me that languages without MI (such as Java) are seriously crippled for certain paradigms, although Java's MI (extends one class, implements many others) is sufficient for a lot of other paradigms.
Perl is somewhere between C++ and Java on MI. Many of perl's classes don't really have any useful constructor. Object types which don't need any construction, but can mix in code fragments (which is impossible in Java, but not C++) are often called "plugins" in Perl. This type of MI is incredibly useful in those problem spaces. And you may not have even realised you were using MI - many plugins "use base" on themselves for you (or equivalent). It's kind of an abuse on Exporter... but it works. And you can still choose to override these functions to extend them - giving you the polymorphism you need.
| [reply] |
|
|
Polymorphism encourages encapsulation, but absolutely requires inheritance.
Completely false.
package RubberDuck;
sub new { bless {}, shift }
sub quack { print "Squeak!\n" }
sub DESTROY { print "RubberDuck goes back in the toybox.\n" }
package Mallard;
sub new { bless {}, shift }
sub quack { print "Quack!\n" }
sub DESTROY { print "Mallard flies away.\n" }
package main;
for my $duck_class (qw( RubberDuck Mallard ))
{
my $duck = $duck_class->new();
$duck->quack();
}
Duck typing, without inheritance.
Polymorphism has ever so much more to do with method dispatch and type equivalence (in languages that care about such things and in situations where it matters) than it does about inheritance, which is merely an implementation detail of how to reuse code and to mark type equivalence in some cases. | [reply] [d/l] |
|
|
|
|
|
|
|
|
The heart of the big benefits from OO is encapsulation, not inheritance.
The heart of the big benefits from OO is polymorphism.
As you two demonstrated, this is obviously up for debate. Any authoritative statement either way is bound to be disagreeable to one person or another. In this case, though, I think you're both right.
A large part of good programming practice is finding the right abstractions for certain tasks. Polymorphism and encapsulation are both ways to abstract one thing or another, and I think objects are pretty good at doing both of those.
Incidentally, I usually use object oriented techniques more for encapsulation, as Tilly suggests, than polymorphism, but I think they are two sides of the same coin. You can have one without the other, but they're not mutually exclusive.
| [reply] |
|
|
I disagree. More specifically while I agree that encapsulation is possible without OO, I believe that most of the advantages that most programmers saw when they moved to OO was that they began to use more encapsulation because it became simpler to do so.
I also disagree that you can't do polymorphism without OO. There are plenty of ways to write code such that you'll get polymorphism. One obvious approach is to emulate how OO works by hand (this turns out to be fairly simple).
About multiple inheritance, it sounds to me like you think that MI is a great idea, but it just keeps on not coming up as a good solution for you. However I'm going to guess that if you ran across a problem today that looked like what you used it for 9 years ago, you'd probably find that there was a perfectly good single inheritance solution available that you now have the programming maturity to find.
Also note that I differentiate multiple inheritance from attempts to create mixins. For one thing mixins have the distinct advantage that you avoid the complications around how to decide which method to dispatch to when unrelated parents implement different versions. Since most of my complaints about MI have to do with exactly that problem, avoiding it does not seem to me to be a small change in semantics.
That said, I generally don't like mixins in Perl, though I do in Ruby. The difference, as I've said many times, is how many uses I can amortize the effort of learning the interface over.
| [reply] |
|
|
The heart of the big benefits from OO is encapsulation, not inheritance.
Interesting observation, open to debate. I am new to OO, and intuitively, it is inheritance that I see as a bigger benefit than any other OO properties. In fact, intuitively, inheritance is what I understand better than encapsulation or polymorphism, etc.
Yes, multiple inheritance (MI) can cause a lot of problems, especially if using modules that were built by others, and hence, were not designed from ground up to be inherited from in conjunction with other modules. But life is complicated, and more often than not involves MI. After all, we all inherited from a dad and a mom, with hopefully no clashes and dire consequences.
That said, should not my approach, stated in my second post above, avoid the problems of same-named methods clashing and overriding each other? Except, it doesn't work all the way. Here is the real code I am trying out. I want to use a config file and connect to an email server. For this, I use Config::Simple and Mail::IMAPClient. Thus far I was using it normally and it works. Lately I decided to OO-ize it thusly --
package Mypackage;
use Config::Simple;
use Mail::IMAPClient;
use strict;
sub new {
my ($class) = @_;
bless {}, $class;
}
sub load_config {
my ($self, %a) = @_;
return new Config::Simple("$a{cfg}");
}
sub connect_to_imaphost {
my ($self, %a) = @_;
return Mail::IMAPClient->new(
Server => $a{IMAP_HOST},
User => $a{EM_UNAME},
Password => $a{EM_PWD},
Uid => 0,
Debug => $a{DEBUGGING},
);
}
sub other_methods... {}
### and then, in my script...
use Mypackage;
my $a = Mypackage->new();
# $a has all the methods of Mypackage
$f->load_config(cfg => "my.conf")->import_names();
# this works and imports all the config vars
my $imap = $a->connect_to_imaphost(
server => $IMAP_HOST,
user => $EM_UNAME,
password => $EM_PWD,
debug => $DEBUGGING,
);
my $msg_count = $imap->message_count("$INBOX");
# the vars above have been imported via Config::Simple
# the above, however, fails with the following
##Not connected at ./myscript.pl line 48
##Error sending '1 STATUS inbox (MESSAGES)' to IMAP: at ./myscript.pl
+ line 48
In the above code, instances of B and C are made only when called for. In a way, I have achieved encapsulation, and made A inherit the properties of both B and C, but only when desired. Thereby, hopefully, avoiding any problems.
Question is -- is the above code ok? If yes, why is it not working. If no, what am I doing wrong?
Many thanks.
--
when small people start casting long shadows, it is time to go to bed
| [reply] [d/l] |
|
|
Your problem is that connect_to_imaphost is returning the new imap object but maintains no connection between that and the object that you created. They know nothing about each other.
About inheritance, the problem with inheritance is that it is "action at a distance". The behaviour of your code depends on code somewhere over there which you're not looking at right now. Tracking down cause and effect therefore becomes more tricky, and you've opened up the possibility of negative interactions between the functions that you write and the ones that the parent class depends on. That is, you've introduced tight coupling between two pieces of code that are maintained separately.
The strength of inheritance is, of course, that you can avoid writing the same code multiple times in multiple places.
Whether the advantages of inheritance outweigh the disadvantages is situation dependent, and sometimes a matter of opinion. But it is a matter of fact that many people have managed to dig themselves into deep holes using inheritance when it wasn't really appropriate.
By contrast encapsulation and information hiding has much more clear-cut benefits and more minor risks.
I'd suggest reading Code Complete 2 for more detail on this theme. (The first edition didn't cover OO, but did explain enough about encapsulation and information hiding to make it clear what the advantages are.)
| [reply] |
|
|
|
|
|
|
Re: Understanding 'Multiple Inheritance'
by Anonymous Monk on Mar 07, 2005 at 09:25 UTC
|
Multiple inheritance in Perl is major pain in the ass. Partially because of lack of language support to do OO, and partially because people tend to populate their objects from the constructor, instead of using separate methods for that.
If for instance, B and C used a constructor that looked like:
sub new {
bless {}, shift;
}
and then initialization routines like this:
package B;
sub init {
my $self = shift;
@$self {qw /_foo _bar/} = @_;
}
sub init {
my $self = shift;
@$self {qw /_baz _qux/} = @_;
}
your package could look like:
package C;
use A;
use B;
our @ISA = qw /B C/;
sub new {bless {}, shift}
sub init {
my $self = shift;
$self -> B::init(@_[0, 1]);
$self -> C::init(@_[2, 3]);
}
Of course, it gets worse if one or more of the packages uses a filehandle, or an array to implement to object, or if two of the inheriting classes use the key (but to find out, you have to break encapsulation).
In short, multiple inheritance in Perl is only possible if all of the classes you inherit have been implemented with this in mind (most classes don't - noone really teaches for this possibility, it's much easier to dismiss it with the statement that multiple inheritance is not done), or if you are lucky and willing to break encapsulation.
It seems that for your B and C, you're lucky, and can do it by breaking encapsulation.
As for question 3, sometimes it's good to admit defeat, and use a language with better OO support than Perl. (Which are easy to find, as it's hard to find a language with worse support) | [reply] [d/l] [select] |
Inheritance can be messy in Perl. Multiple inheritance can be disastrous.
by skyknight (Hermit) on Mar 07, 2005 at 14:14 UTC
|
I love Perl, but it's "object orientation" is incredibly brittle as a
result of it being so damned ad-hoc. Really, Perl is name space
oriented, and the actual storage of member data is up to the creator
of a class. Typically this is done by blessing a hash into a class,
and then making the "member variables" be elements of said hash.
This, apart from being inefficient, is functional in the case of stand
alone classes. It becomes dangerous when you inherit from another
class as name clashes can occur, and the dangerousness increases
geometrically with the size of your inheritence family.
In either C++ or Java, member variables are explicitly specified as
being either public, protected, or private. If they are private, then
derived classes cannot even see them, and if they are protected, then
descendants can see them, but there is name-spacing such that in the
case of a base and derived class having a duplicate name, there are
actually two distinct variables. In this case, I believe access to a
variable in a derived class defaults to the one in the child class,
and (in the case of protected and public) the parent class's variable
can be accessed by explicitly naming it with the parent class's name.
The base class will know nothing of the child class's variable by the
same name.
Unfortunately, in Perl you're just stuck with everything being in a
single hash. The best thing I can think to do is to have your hash be
two-tiered, with the first level of keys being class names, and the
second level being member data for the corresponding class. This
saves you from having classes in a hierarchy carelessly step on one
another's data. So, instead of seeing methods like this...
sub set_foo {
my ($self, $val) = @_;
$self->{foo} = $val;
}
you would instead have stuff like...
sub set_foo {
my ($self, $val) = @_;
$self->{ref($self)}{foo} = $val;
}
Of course, this has two problems... First of all, everyone has to
agree to do it. If you have ultimate control over all classes in the
hierarchy, then maybe this isn't a problem, but what if you want to
write classes derived from a class provided by a third party library?
You are at their mercy. Also, another problem is that unlike C++ and
Java which effect this name-spacing at compile time, this solution in
Perl does it at run time, and we end up paying a penalty for having to
drill through not one level of a hash, but two. It's bad enough as it
is that we have to deal with hashes pretending to be objects. Slowing
down member variable access by a factor of two might be unacceptable.
As best I can tell, Python also drops the ball on inheritance. The
local dictionary associated with every Python object is basically the
same thing as what we have in Perl, albeit built into the language
core explicitly.
Does anyone know if Perl6 is going to suck less in this regard? I
would truly love to have proper objects in Perl. The present ones are
disappointing.
| [reply] [d/l] [select] |
|
|
Pattern I see repeated over and over is that people who complain about Perl's OO got their notions of what's good OO from programming in other languages. Perl's OO in and of itself is powerful and useful; it's just a matter of grokking the fu (so to speak).
Of course, necessity (perceived or otherwise) being what it is, there are numerous modules out there which seek to give a more rigorous form of OO to Perl - mostly in the Class space.
I generally try to make perl's intrinsic OO bits work for me, whenever possible.
Most of the time, I have full control of the class hierarchy, which, as you said, obviates the problem of member name conflicts. Even so, it's possible for things to get hairy; so to head that off, I take the Java approach to multiple inheritance, and stipulate that only one line of inheritance can define member data.
Occasionally, I do use other people's classes which are designed to be subclassed; in such cases, if I need to add member data, I'll go to some extreme in the naming:
package Squatch::Substandard;
use Squatch::Standard;
use base qw(Squatch::Standard);
sub dingies {
return $_[0]{'Squatch::Substandard::dingies'}
}
Unfortunately, in Perl you're just stuck with everything being in a single hash.
It's true that you only get one scalar (a reference) to bless into objecthood;
but it doesn't have to be a hash. And in any case, there's other ways of dealing
with this "limitation" than:
to have your hash be two-tiered, with the first level of keys being class names, and the second level being member data for the corresponding class.
And anyway, once you've done that, you're 69% of the way to having extension by aggregation,
which (arguably) is a better way to do type extension anyway.
another problem is that unlike C++ and Java which effect this name-spacing at compile time, this solution in Perl does it at run time
That could be said about everything else in perl. If you've drunk the Perl kool-aid, you don't bat an eye at issues like this.
Does anyone know if Perl6 is going to suck less in this regard?
Why don't you read the Perl6 docs and decide for yourself?
Your definition of "suck" may be substantially different than a lot of perl users'.
| [reply] [d/l] |
|
|
It's true that you only get one scalar (a reference) to bless into objecthood; but it doesn't have to be a hash.
Indeed, but don't even think for a microsecond that that's a feature. It's not. Try subclassing from the class that uses references to arrays as objects - with a variable amount of data members. Try subclassing from a class that's using blessed closures as objects, and having the need to store instance data as well. Try subclassing from scalar references. Try doing MI from two classes that both use array references as objects - even if you have no instance data to store yourself.
If you want to play friendly with potential subclassers, you either use references to hashes, or a technique that uses only the reference, and not what's it pointing to, like some fly weight pattern variations, or inside out objects, or Lexical::Attributes.
| [reply] |
|
|
|
|
|
|
|
| [reply] |
Re: Understanding 'Multiple Inheritance'
by tphyahoo (Vicar) on Mar 07, 2005 at 11:10 UTC
|
Bit off topic, but Perl6 is supposed to be better at multiple inheritance: apocalypse 12
Anyone have an opinion on whether this is good/important/true?
FWIW, I've never been real tempted by MI, but I'm still getting my sea legs in perl. | [reply] |
|
|
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] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|