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

I'm porting a small Java application to Perl, using Class::Std. Now I have a class with a constructor that calls the superclass constructor with certain required fields filled in. An example in Perl might be:
package myclass; use Carp qw(croak); sub new { my ($class, $value) = @_; croak "required value missing" if @_ == 1 or !$value; return bless \$value, $class; } package mysubclass; use base qw(myclass); sub new { my ($class) = @_; return bless $class->SUPER::new(23), $class; }
Now in Class::Std, superclass "constructors" (ie BUILD and START) are called automatically. However, there doesn't seem to be a way to set or override the arguments for a superclass in a subclass. I'd like to do something like this:
package myclass; use Class::Std; my %value :ATTR( :init_arg<value> ); package mysubclass; use base qw(myclass); sub BUILD { my ($self, $ident, $args) = @_; $args->{value} = 23; }
This doesn't work. Even though Class::Std calls BUILD(), then initialises the object from the constructor arguments, and then calls START(), the third argument passed to each BUILD/START method is specific to that method - its discarded afterwards. There doesn't seem to be any way to influence the arguments in this way. Doing this:
Missing initializer label for myclass: 'value'. Fatal error in constructor call
Anyone got any ideas before I open a ticket in RT?


Replies are listed 'Best First'.
Re: Overriding arguments in Class::Std superclass constructors
by lima1 (Curate) on Aug 11, 2006 at 14:35 UTC
    Although not exactly what you want:
    mysubclass->new({ myclass => { value = 23 }});
    works. another solution would be to add a default value to myclass->value so that a missing initializer label does not throw a fatal error. To set a new default value, overriding the START method seems to work:
    package myclass; use Class::Std; my %value :ATTR( :name<value> :default(1)); 1;
    package mysubclass; use base qw(myclass); sub START { my ($self, $ident, $arg_ref) = @_; $self->set_value(123) if !defined $arg_ref->{value}; } 1;
    UPDATE: original version ignored initializer labels.
      Yeah, the first sucks because I'd have to do it everywhere I create objects, and all those places have to "know" more than they should.

      My workaround is basically the same, except that I removed the init_arg and made myclass::START do the croak-or-set itself.

      Thanks for the ideas. I've opened ticket 20966 in RT.

Re: Overriding arguments in Class::Std superclass constructors
by jdhedden (Deacon) on Aug 11, 2006 at 15:59 UTC
    Try using Object::InsideOut. It will do just what you want:
    #!/usr/bin/perl use strict; use warnings; package My::Class; { use Object::InsideOut; my @value :Field('Accessor' => 'value'); my %init_args :InitArgs = ( 'value' => { 'Field' => \@value, 'Mandatory' => 1, }, ); } package My::Class::Sub; { use Object::InsideOut qw(My::Class); sub _preinit :PreInit { my ($self, $args) = @_; $args->{'value'} = 23; } } package main; my $obj = My::Class::Sub->new(); print($obj->value(), "\n");

    Remember: There's always one more bug.
      Unfortunately I didn't learn about Object::InsideOut until a few weeks into this project, so its too late to switch. Possibly for the next version, we'll see. Thanks for the example though, its good to know the possibility exists.
Re: Overriding arguments in Class::Std superclass constructors
by fce2 (Sexton) on Aug 29, 2006 at 08:08 UTC
    For any future searchers ..

    I saw Damian yesterday and chatted to him about it. The way to do it is for the subclass to override new:

    sub new { my ($class, $args) = @_; $args->{value} = 23; return $class->SUPER::new($args); }
    new() is actually installed into the parent class by Class::Std (rather than inherited), and because its receiving the caller, its able to do the right thing.

    This is a workaround though. The next release of Class-Std will have a PREBUILD() method that runs before BUILD(). It goes in the opposite direction ("up" the inheritance tree) and allows the arguments to be changed before BUILD() gets them. So this will simply become:

    sub PREBUILD { my ($class, $args) = @_; return { %{$args}, value => 23 }; }