vvch
vvch

Reputation: 315

Perl class attribute inheritance

I have class attribute, e. g., counter of created objects in some base class,

package A;
our $counter = Counter->new; # not just a counter in fact, so initialization code should be inherited by descendants as well

sub new {
    $counter++;
    bless {}
}
sub get_counter {
    $counter
}

package B;
use base 'A';

package main;
B->get_counter();

I want package B to have his own copy of this class attribute (e. g., counting objects of B class only), and all inherited methods from package A should deal with this copy. What is the correct way to implement this in plain perl and in Moo/Moose? Seems like MooX::ClassAttribute can not be inherited. One ugly solution found is to repeat attribute initialization code in each descendant and use symbolic dereference like ${"${class}::counter"} in ancestor's methods to access this attribute with actual package name. But seems like there should be more elegant way.

Upvotes: 1

Views: 293

Answers (1)

amon
amon

Reputation: 57600

The default Perl object model has no concept of class attributes. And there's no kind of hook like “when a new subclass is created, run this code”.

Instead, the base class could maintain a hash of counters, using the class name as keys:

package A;
my %counters;

sub new {
  my ($class) = @_;
  my $counter = $counters{$class} //= Counter->new;
  $counter++;
  return bless {} => $class;
}

sub get_counter {
  my ($self_or_class) = @_;
  my $class = (ref $self_or_class) || $self_or_class;
  $counters{$class};
}

package B;
use parent -norequire, 'A';

This will create a new counter when an instance of a subclass is created. Note that the first argument to a method is either the class name or the object instance. We need to use that in new() as the hash key. In get_counter() I've written this in a way that the method can be called on both a class and an object to the same effect.

A similar technique is known as inside-out objects, where store object fields in a hash held by the class, so that the object itself doesn't contain any data.

(Why parent instead of base? The parent module only does inheritance, whereas base also integrates with the fields pragma which you should not use.)

Upvotes: 3

Related Questions