views:

170

answers:

4

I have two classes: a base class, Foo::Base and a derived class, Foo::Base::Sub. I want to have Foo::Base::Sub do some type and data checking on the constructor`s argument--a hash--before blessing it. I've tried overriding Foo::Base->new's constructor, doing the checks and then calling Foo::Base->new (since the code would be exactly the same):

package Foo::Base::Sub;

sub new {
    ...check argument's type and data...
    Foo::Base->new(%my_hash)
}

The problem is that by calling Foo::Base's constructor, the hash will now be blessed as a Foo::Base object and not a Foo::Base::Sub object. The obvious solution is simply to put the code from Foo::Base::new into Foo::Base::Sub::new but then I'm repeating code. The other thing is that Foo::Base is not mine--thus I'd like to avoid having to modify it after the module has loaded or forking it unnecessarily.

It seems to me that this problem must have come up before and so there must be a canonical solution. Moreover, it really touches on type coercion which is generally not an issue Perl.

So is there a simple modification or am I going about this the wrong way?

A: 

You might want to look at the various ways of invoking super. The SUPER module may work although I havent tried it myself.

ennuikiller
That's a good module, although its documentation is way out of date. Reading its source is quite educational, however.
Ether
Interesting. I`m going to go over the source tonight.
gvkv
+8  A: 

A standard Perl idiom is to use SUPER to call up the inheritance chain:

@Foo::Base::Sub::ISA = qw(Foo::Base);

sub new {
    my $package = shift;

    my $self = $package->SUPER::new();

    # Other subconstructor stuff here

    return $self;
}

As noted in the comments, Foo::Base's constructor must use the two-argument form of bless:

sub new {
    my $package = shift;

    my $self = bless {}, $package;

    # Other superconstructor stuff here

    return $self;
}

When the superclass' constructor is called, $package will be the subclass.

Commodore Jaeger
Note that this only works if the superclass constructor uses the two-argument form of `bless` in order explicitly set the package name of the new object. (This is why you should always use `bless $self, $class`)
friedo
Another way of saying all that is that either the constructor of Foo::Base is broken or it's not.
innaM
Use the 'parent' pragma rather than manipulating @ISA directly.
Ether
Cool. I've only used SUPER to guarantee the correct superclass's method is called. Seeing your answer motivated me to take a closer look at perlobj and it all makes sense now. Thanks.
gvkv
@Ether: Thanks for the heads up.
gvkv
@Ether => everyone always says to `use parent` or `use base` but what is the reasoning behind that? i can't imagine that the usage of @ISA is going to change and `our @ISA = qw/Foo::Base/` doesn't seem all that complicated and tells you exactly what is happening, and will explicitly complain about a base class not being required if it needs to be
Eric Strom
@Eric Strom: multiple reasons; there are some funky order-of-compilation problems you can have if you set @ISA at run time instead of compile time (which BEGIN { our @ISA ... } would also address) and it's handy to enforce loading modules you subclass rather than (accidentally or on purpose) relying on some other module to have loaded them. base also deals with subclassing a class that use fields.pm (a rare and I hope getting rarer beast)
ysth
@Eric Strom: no, it won't always complain: `perl -wle'{ package Quux; sub quux { print "hi" } } { package Foo::Bar::Baz; @ISA = qw/Foo::Bar Quux/ } Foo::Bar::Baz->quux'` Here, the use of Foo::Bar::Baz automatically creates an empty Foo::Bar package, so you get no warning that `use Foo::Bar` was omitted.
ysth
One other caveat to be aware of: `$package->SUPER::new` actually calls the superclass of *the current package* (that is, as designated by the latest `package` declaration, or `main` if there was none), **not** of $package. This difference can usually be ignored as `$package` is either the same as or a child of \_\_PACKAGE\_\_, but if you're doing some funky polymorphism you may see results that can take a while to debug. :)
Ether
@ysth => good info, do you have any references that go into the details of the compilation order problems? i've mostly been avoiding base and parent since i find most of my sub-packages are small enough to inline, and the same file syntax is a little awkward.
Eric Strom
@Eric Strom: no, I don't; it comes up on perlmonks from time to time, where people are doing recursive uses or have mainline code in modules trying to call methods that end up not being in the inheritance tree yet. I'm having trouble coming up with a short example from memory. I like parent's same file syntax.
ysth
+7  A: 

I'm used to split this to two parts, new and init.

package Foo::Base;

sub new {
  my $class = shift;
  my $self = bless {}, $class;
  return $self->init(@_);
}

sub init {
  my ($self, @params) = @_;
  # do something initialization and checks
  return $self;
}

package Foo::Sub;

use base 'Foo::Base';

sub init {
  my ($self, @params) = @_;
  # do something initialization and checks
  $self = $self->SUPER::init(@params);
  # do something other if you wish
  return $self;
}

Note that 'Foo::Sub' doesn't implement new constructor.

Hynek -Pichi- Vychodil
Two-phased construction is less desirable if init() fails and you want the entire construction to fail as a result -- you'll end up with a partially-constructed object in an unknown and probably unusable state.
Ether
I like this answer from a pedagogical point of view but I'm loath to separate construction into two parts. Especially when calling class->new(...) is so standard. Thanks for your answer.
gvkv
When `init` fails then `new` fails because there is not any `eval` and `init` is called *inside* `new`. That's all.
Hynek -Pichi- Vychodil
When the init() fails, you just return nothing, which is the same thing that would happen if new() fails.
brian d foy
A: 

While the 'super' answers do describe what you want here, you can (in the more general case) call method code in arbitrary packages by doing something like this:

 $object = Foo::Class->new(...);
 $object->Bar::Class::method(@args);

Bar::Class::method will proceed as normal, something like:

 package Bar::Class;
 sub method {
   my ($self, @args) = @_;

only $self will be of a different class than usual. Naturally, doing this instead of SUPER a lot is probably a sign you're doing something wrong.

Compare also NEXT and Perl 5.10's 'mro'.

fennec