Quoth Ivan Shmakov <
onei...@gmail.com>:
> (ISTR that there was a discussion of this somewhere in the
> Perl's documentation, but I cannot find it right now.)
>
> So, I'm implementing a subclass SubClass to the class Class:
>
> package SubClass;
> use base 'Class';
base.pm is deprecated. It tries to set up data inheritance using the
also-mostly-deprecated fields pragma, which is not normally useful. Use
parent.pm instead, which just sets up inheritance, or just set @ISA
directly.
> Then, how do I write a suitable constructor for SubClass, given
> that I need to associate some additional data with the object?
This has been a perennial problem with Perl OO since the beginning, so
there are lots of different solutions. ('Problem' in the sense of
'something which requires a solution', rather than 'design flaw'; there
are times when Perl's flexibility in this regard is useful, as well as
times when the lack of an 'easy things should be easy' solution is
irritating.)
The oldest is
fields.pm which I mentioned above; since the intention of
this pragma was to interact with the now-removed pseudohashes feature of
perl 5005 and 5.6, it is not a lot of use these days. Nowadays it's most
common to use one of the Class::Accessor family of modules, or Moose.
Both of these deal with the problems you mention below by enforcing some
structure on the internal representation of the object. Normally they
both implement objects as hashrefs keyed by 'attribute name', though
Moose at least is capable of handling objects with other implementations
if necessary.
My recommendation would be to just use Moose. It has a certain amount of
overhead, but for that you get an extremely powerful and flexible
system.
> Indeed, I may know that the object is a reference to a hash, so:
>
> sub new {
> my ($class, $param_my, @param_super) = @_;
> my $self
> = Class::new ($class, @param_super)
> or die ();
> $self->{"SubClass::param_my"}
> = $param_my;
I would say that you should not do this: that is, you should not access
the underlying representation directly. That should be the job of some
piece of code dedicated to that purpose, either implemented directly in
the base class or pulled in from a module. Both Class::Accessor and
Moose provide ways to create new attributes in subclasses which don't
involve playing with the underlying hash directly.
> ## .
> $self;
> }
>
> However, do I understand it correctly that once the Class'
> author switches to, say, a reference to a list, I'm out of my
> luck?
You do. The simplest answer is 'don't do that': that is, the
'subclassing interface' should be considered part of the contract a
class creates, and if it changes this should be documented as a breaking
change. Unfortunately this is often not done; fortunately, a hashref
keyed by attribute name is so much the most common class representation
that it's often possible to subclass on that assumption anyway.
> I wonder, will it help if the Class' author provided a way for
> the subclasses to associate arbitrary data with the object?
> Consider, e. g.:
>
> package Class;
>
> sub appdata {
> my $self = shift;
> return ($self->{"appdata"} = shift)
> if (@_);
> return ($self->{"appdata"});
> }
>
> package SubClass;
>
> ## a hack: replace appdata with a "nested" version?
> sub appdata {
> my $self = shift;
> my $myself = $self->SUPER::appdata ();
> return ($myself->{"appdata"} = shift)
> if (@_);
> return ($myself->{"appdata"});
> }
>
> sub new {
> my ($class, $param_my, @param_super) = @_;
> my $self
> = Class::new ($class, @param_super)
You should call this as a method: Class might inherit its ->new method.
my $self = $class->Class::new(@param_super)
You should probably also consider using SUPER or (better) next::method
instead. (SUPER is only adequate for the case of single inheritance.) Or
just use Moose...
> or die ();
> $self->SUPER::appdata ({ "param_my" => $param_my });
> ## .
> $self;
> }
It's not terribly clear to me how this is supposed to work--how do you
get at 'param_my', for instance?--but I don't think it subclasses
correctly. You've carefully kept the appdata hashes for the different
classes separate, but there's only one ->appdata method, so a method in
class Class calling ->appdata will see the same hash as one in class
SubClass.
If I were going to do something like this I might do this:
package Class;
sub appdata {
my ($self, $for) = @_;
$for //= caller;
$self->{$for} ||= {};
}
and then use that in all classes, for all attributes. So:
package Class;
sub new {
my ($class, $one, $two) = @_;
my $self = bless {}, $class;
my $data = $self->appdata;
$data->{one} = $one;
$data->{two} = $two;
$self;
}
package SubClass;
sub new {
my ($class, $one, $three) = @_;
my $self = $class->Class::new($one, "default for two");
my $data = $self->appdata;
$data->{three} = $three;
$self;
}
which gives us 'package scoped' or 'class scoped' attributes, with the
possibility of peeking at other classes' attributes if we need to.
Ben