OlegG has asked for the wisdom of the Perl Monks concerning the following question:

I have 3 classes:
LWP::UserAgent::Cached
LWP::UserAgent::Determined
LWP::UserAgent::Proxified

All of them are direct subclasses of LWP::UserAgent. Each adds some new methods and options to LWP::UserAgent. Now I want useragent which has all this options.
How about new pragma (or may be something similar already exists?)? I would call it composition (your suggestions?).
use composition LWP::UserAgent => LWP::UserAgent::Proxified => LWP::UserAgent::Determined => LWP::UserAgent::Cached; # now LWP::UserAgent::Cached->new() creates useragent with all support +ed options
or even
use composition -new => LWP::UserAgent => LWP::UserAgent::Proxified => LWP::UserAgent::Determined => LWP::UserAgent::Cached => LWP::UserAgent::Powerfull; # LWP::UserAgent::Powerfull was autogenerated # LWP::UserAgent::Powerfull->new() creates useragent with all supporte +d options
This pragma will simply modify @ISA of each specified package: LWP::UserAgent::Proxified as before inherits LWP::UserAgent, LWP::UserAgent::Determined now inherits LWP::UserAgent::Proxified and LWP::UserAgent::Cached inherits LWP::UserAgent::Determined.

Any comments? Is something similar already exists?

Replies are listed 'Best First'.
Re: base class manipulation: Is there something similar on CPAN?
by JavaFan (Canon) on Jan 16, 2012 at 12:05 UTC
    You want a module that pushes onto an array?

    I would do:

    package LWP::UserAgent::Powerfull; use LWP::UserAgent::Proxified; use LWP::UserAgent::Determined; use LWP::UserAgent::Cached; our @ISA = qw[LWP::UserAgent::Proxified LWP::UserAgent::Determined LWP +::UserAgent::Cached];
    Or, if for whatever reason, you insist of having the indicated chain:
    package LWP::UserAgent::Powerfull; use LWP::UserAgent::Proxified; use LWP::UserAgent::Determined; use LWP::UserAgent::Cached; push @LWP::UserAgent::Determined::ISA, "LWP::UserAgent::Proxified"; push @LWP::UserAgent::Cached::ISA, "LWP::UserAgent::Determined"; our @ISA = "LWP::UserAgent::Cached";
    assuming that LWP::UserAgent::Proxified already inherits LWP.
      Your second example looks more correct, but in fact should be:
      foreach my $parent (@LWP::UserAgent::Determined::ISA) { if ($parent eq 'LWP::UserAgent') { $parent = 'LWP::UserAgent::Proxified'; last; } }
      instead of push.
      Module can automate this routine actions and make some additional checks.
Re: base class manipulation: Is there something similar on CPAN?
by tobyink (Canon) on Jan 16, 2012 at 13:15 UTC

    In an ideal world, these user agent classes would have been written from the ground up as Moose roles, so could be combined however you like.

    Something like this should work though...

    use 5.010; use Class::Load qw/load_class/; sub compose { load_class($_) foreach @_; # Note "state" is Perl 5.10 mojo. For back compat, use # "my $count = 0" OUTSIDE the sub definition. That is, # create a closure over it. state $count = 0; my $package = sprintf('%s::__COMPOSED__::X%04x', __PACKAGE__, ++$c +ount); { no strict 'refs'; @{"$package\::ISA"} = @_; } $package; } my $class = compose(qw/LWP::UserAgent Data::Dumper/); say $class; my $obj = $class->new; say $obj; say $obj->can('agent'); say $obj->can('Dumper'); # Or even... my $obj2 = compose(qw/LWP::UserAgent Data::Dumper HTTP::Request/)->new +;

    The problems creep in when the many classes you're inheriting from all override the same method. For example, LWP::UserAgent::Proxified, LWP::UserAgent::Determined and LWP::UserAgent::Cached might all override LWP::UserAgent's "request" method in different ways.

    If LWP::UserAgent::Proxified inherits from all of them, then although Perl's method resolution order is well-defined, it will only call the overridden method from a single one of its child classes.

    This is where Moose roles - and in particular, method modifiers - really shine. The base class provides the default "request" implementation, and he Proxified, Determined and Cached roles provide modifiers for the default implementation. The roles can usually be combined safely, with the various modifiers all getting applied to the "request" method.

      Thanks for the answer
      Really didn't know that @{"$package\::ISA"} = @_; will make a package. I was going to use eval :)

        It is also possible to do it with strict mode switched on too, but it's generally not worth the extra effort. See door #7 from last years' advent calendar.

Re: base class manipulation: Is there something similar on CPAN?
by OlegG (Monk) on Jan 16, 2012 at 18:14 UTC
    I implemented proof of concept :)
    use composition LWP::UserAgent => LWP::UserAgent::Determined => LWP::UserAgent::Cached => LWP::UserAgent::Super; use strict; sub print_isa { my ($pkg, $level) = @_; print "\t"x$level, $pkg, "\n"; no strict 'refs'; for my $parent (@{"$pkg\::ISA"}) { print_isa($parent, $level+1); } } print_isa('LWP::UserAgent::Super');
    which outputs
    LWP::UserAgent::Super LWP::UserAgent::Cached LWP::UserAgent::Determined LWP::UserAgent LWP::MemberMixin
    Just works :)