in reply to Re: Understanding 'Multiple Inheritance'
in thread Understanding 'Multiple Inheritance'

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

Replies are listed 'Best First'.
Re^3: Understanding 'Multiple Inheritance'
by tilly (Archbishop) on Mar 08, 2005 at 01:55 UTC
    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.)

      Thank you tilly. Not only have you helped me understand the problem of "tight coupling" vs. inheritance, you have set me on the right path by pointing out the pitfalls in my current path. I will try another tact with my code... encapsulation and information hiding would be worthy causes to pursue, I don't even know how I can do that when multiple classes are involved without resorting to multiple inheritance.

      In any case, this has been an instructive thread, if I may say so myself.

      --
      when small people start casting long shadows, it is time to go to bed

        I think you're viewing inheritance as the solution without fully understanding just what the implications are. (which is what your original post hinted at). I'll try to explain it simply:

        When you inherit, you make the other module's methods available to whomever might be using your module. When you multiply inherit, you make all methods from all inherited modules available, which may cause interaction problems.

        Typically, when you're looking at when you use inheritance, you look for is-a relationships, rather than has-a relationships. In your particular case, it looks to me as if the IMAP::Client relationship is shaky. (if you want to it almost identically to an IMAP::Client, then it's an is-a relationship. If you want the user of your program to not deal with the IMAP::Client directly, then it's a has-a relationship. It look as if the relation with Config::Simple is definately has-a. Here is the basic logic to deal with the has-a relationships:

        package MyPackage; use strict; use IMAP::Client; use Config::Simple; use Carp; sub new { my $class = shift; return bless {}, ref($class) || $class; } sub load_config { my $self = shift; $self->{'config'} = Config::Simple->new(@_); return $self->{'config'}; } sub connect_to_imaphost { my $self = shift; my $config = $self->{'config'} or croak "ERROR : Connect to imaphost without loading config"; $self->{'imap'} = Mail::IMAPClient->new( Server => $config->{'IMAP_HOST'}, User => $config->{'EM_UNAME'}, .... ); return $self->{'imap'}; } # accessor functions sub imap { return shift->{'imap'}; } sub config { return shift->{'config'}; }

        Now, when you want to use your module, you can do something like:

        use MyPackage; my $pkg = MyPackage->new(); $pkg->load_config('my.conf'); $pkg->connect_to_imaphost();

        And should you need to access the underlying objects:

        my $msg_count = $pkg->imap()->message_count( ... );

        (You don't want to store the return from $pkg->imap() for long term storage, in case the system has to disconnect/reconnect, or something else that change the IMAP::Client object that is associated with your MyPackage object.)

      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.

      Indeed. And IMO, having proper encapsulation and information hiding is a necessary requirement to be able to do inheritance well.