http://www.perlmonks.org?node_id=29137

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

I just recently bought and digested Object-Oriented Perl by fellow australian, damian conway. great book.

Even after musing over damian's insights, dare i say that OO programming in perl could still be implemented much more cleanly/elegantly (IMHO).

On that note, i am searching for enlightened monks' opinions on the best way to implement class data such that it may be cleanly inherited by subclasses.

here are (my) 2 top contenders:

So, opinions anyone? I can see that it wouldn't be too difficult to provide a Class_Data method which accessed/ manipulated the namespace of the calling object's classname, but that's *really* pushing the limits of what most people accept as "OO abstraction".

Replies are listed 'Best First'.
Re: opinions on the best way to inherit class data
by chromatic (Archbishop) on Aug 23, 2000 at 05:47 UTC
    What about a copy constructor? Something like the following might even be inheritable, or set class-based defaults:
    package Original; sub new { my $class = shift; my $other = shift; # hash ref, optional my $self = { name => 'the original', rating => 'supreme commander', boots => 'laced to the knee', }; if ($other) { foreach (keys %$other) { $self{$_} = $other->{$_}; } } bless($self, $class); return $self; } sub copy { my $self = shift; return $self->new($self); }
    That's untested, and not particularly beautiful. If you wanted to make it robust, keep around an array of valid keys for your class data. It's not guaranteed to handle nested references correctly, and it may fail in certain inheritance solutions.

    But it's an idea. perltootc has more interesting ideas.

    If you're really interested in inheritance and smart defaults, separate initialization of the hash from the constructor:

    sub new { my $class = shift; my $self = _init(@_); # private method bless($self, $class); return $self; } sub _init { my %data = ( name => 'supreme commander two', rank => 'even better', toothbrush => 'purple with sparklies', ); if (@_) { foreach (keys %{ $_[0] }) { $data{$_} = ${$_[0]}->{$_}; # yuck, probably wrong } } return \%data; # reference! } sub copy { my $self = shift; return $self->new($self); # pass this as a hash ref }
    And in a subclass:
    sub copy { my $self = shift; my $new = __SUPER__->init($self->new($self)); return $new; }
    Okay, now that's *officially* ugly. Just like a real OO language should be.
      Thanks for the reply, chromatic.

      i think i was perhaps not clear enough about my definition of 'class data', as opposed to 'class defaults', which i believe your response addresses. my apologies.

      some typical 'class data' for example might be:

      # base class package AbstractMyClass; my $_Class_Count = 0; sub new { my $class = shift; my $this = bless( {}, $class ); ref($this) && $_Class_Count++; return $this; } package MyClass; @MyClass::ISA = ('AbstractMyClass'); sub new { my $class = shift; my $this = $class::SUPER->new( @_ ); # this class' init... return $this; }

      if i were to use the code...

      use AbstractMyClass; use MyClass; my $amc = new AbstractMyClass (); my $mc = new MyClass ();

      ...$AbstractMyClass::_Class_Count would be 2, whereas i would have really liked $AbstractMyClass::_Class_Count to be 1, and $MyClass::_Class_Count to be 1. Probably a bad example of the essence of my original question, but nevertheless illustrative of what i am terming 'class data'. thanks, matt aka d_i_r_t_y, since 'zero cool' was taken... ;-)

        Oh, that's what you mean! Here's a sneaky way to do it:
        #!/usr/bin/perl -w use strict; package Superclass; use vars qw ( $AUTOLOAD ); my $classdata = qq| { my \$self = shift; my \$invocations = 0; sub _increase { \$invocations++; } sub number { return \$invocations; } }|; sub new { my $class = shift; if ($class ne __PACKAGE__) { # he he he eval qq|package $class; $classdata |; } my $self = {}; bless($self, $class); $self->_increase(); return $self; } eval $classdata; package Subclass; use vars qw ( @ISA ); @ISA = qw ( Superclass ); package main; print Superclass->number(); my $one = Superclass->new(); print $one->number(); print Subclass->number(); my $two = Subclass->new(); print $two->number();
        If your superclass is in a different module, you can play around with the import subroutine and do it a bit more cleanly.

        This technique has the unfortunate effect of making Subclass->number() return 1 before you create any instance of Subclass. (Inheritance... oh well.)

Re: opinions on the best way to inherit class data
by merlyn (Sage) on Aug 23, 2000 at 09:57 UTC
    How about not thinking of it in terms of "class data" but as the data that belongs to a singleton object, the "class" or "factory"? So the only access is via class methods, and no method returns back the raw entire class data. If a subclass wants to extend or manage this data in a distinct way, it can use SUPER calls to get to the data.

    This is as opposed to "class instance" data, the data that belongs to each singleton class object. For example, if a Dog inherits from Animal, class data for Animal would also be shared by Dog. But class instance data would be distinct individually for Dogs vs Animals, in which case the class methods would be inherited for behavior but not actual data access.

    So, actually, I'm puzzled. Did you mean class data, or class instance data?

    -- Randal L. Schwartz, Perl hacker

useless inheritance of useless class data
by tye (Sage) on Aug 23, 2000 at 07:44 UTC

    I'll play heretic, as usual. (: I consider class data to be an anomoly that is usually not a good idea. And I consider inheritance to be an anomoly that can be very useful but can very easily be used too much. So having your class data inherited is doubly useless. ;>

    Okay, maybe 0.2% of your classes need class data and 10% of them do some limited inheritance so maybe 1 in 5000 even have the potential to worry about this problem.

    So I'd just use:

    package My::Parent; our %ClassData; my $ClassCount; BEGIN { %ClassData= ( this=>"that", foo=>"baz", count=>\$ClassCount ) } sub new { my $this= shift; my $class= ref($this) || $this; my $classData; { no strict 'refs'; $classData= \%{$this."::ClassData"}; } #... $self{foo}= $classData->{foo}; ++${$classData->{count}}; return $self; } package My::Son; use base "My::Parent"; *{ClassData}= \%My::Parent::ClassData; package My::Daughter; use base "My::Parent"; our %ClassData; my $DaughterCount; BEGIN { %ClassData= %My::Parent::ClassData; $ClassData{foo}= "bar"; $ClassData{count}= \( $DaughterCount= 0 ); }

    I'd love to hear why that sucks, because it probably does. Update: I'd prefer the %ClassData be declared const, but I don't have the module handy that lets me do that the proper way for this case.

    Finally, on a less heretical note, if you really want class data, then what you want is a separate class for creating a singleton object that will contain the class data. Because one of the most common mistakes that leads to the belief that class data is desired is not understanding that what you really wanted is a container. And just because you didn't think of why you'd ever want more than one container at a time, that doesn't mean there isn't a use for multiple containers. And it is relatively easy to turn your singleton object into multiple objects.

    Plus, OO implementations often don't handle class data as well as object data, so you might as well put your data into an object so that you get to use all the available tools with it (such as inheritance -- as someone else suggested).

            - tye (but my friends call me "Tye")
RE: opinions on the best way to inherit class data
by tenatious (Beadle) on Aug 23, 2000 at 06:42 UTC

    I'm still new to the OO thing, so I'm not exactly sure what you are looking for here, but since you have the book, you might want to take another look at the chapter on inheritance. I believe you can inherit the whole kit and kaboodle in some kind of initiatizable class or something. e.g.

    package _Initializable; sub new { my ($self,$args); my $private_stuff = "private"; my $class_data = { do stuff here "class_data" => $private_stuff}; bless $class_data, ref($class)||$class; $self->_init($args); # pass them back to the calling mod }
    And then you'd call it like:
    use _Initializable; package Caller; # suck "new" in from _Initializable, along with our # data private to object Caller when we call # $foo=Caller->new($arg) elsewhere @Caller::ISA = qw(_Initializable); # no "new" constructor in any of our stuff... we use _init # to do "construction" # now so that we inherit from _initializable # obviously if you want to provide a class method in # _Initializable for the private data, that wouldn't be too # difficult. sub _init { my ($self,$arg) = @_; do other stuph return $self; }

    As I understand it, any method you put in _Initializable (or any other module you inherit, for that matter) is masked if you make a method with the same name as the one in _Initializable. As I say, I'm pretty new to the whole concept of OO, so I may have completely missed the point. As you can see, most of this is a complete crib from Conway's book, chapter 6.

Re: opinions on the best way to inherit class data
by ZZamboni (Curate) on Aug 23, 2000 at 07:24 UTC
    Below is a very ugly hack that I used some time ago, and that has worked until today, for initial (hard-coded) inheritable class data. I'm sure there is a better way of doing this, and I haven't finished my way through Conway's book yet. The initData method manually "walks" through the @ISA hierarchy in a depth-first fashion, incorporating each class' %DATA hash into the object. So you create an object, call its init method (which calls initData), and it populates the object with the appropriate hashes. So in each package you just have to declare the global %DATA hash containing the initial data

    Ugly, I know, but it works.

    #!/usr/local/bin/perl -w use strict; package A; use vars qw(%DATA); %DATA=('name' => 'A name', 'data' => 'A data' ); sub new { my $class=shift; my $self={}; bless $self, $class; } sub init { my $self=shift; my $class=ref($self); $self->initData($class); # other initialization here } sub initData { my ($self, $class, $visited)=@_; $visited={} unless $visited; # First visit all the superclasses no strict 'refs'; foreach my $c (@{"${class}::ISA"}) { $self->initData($c, $visited); } # Now take my own %DATA hash if (!exists($visited->{$class})) { $self->copyData(%{"${class}::DATA"}); $visited->{$class}=1; } } sub copyData { my $self=shift; my %data=@_; $self->{$_}=$data{$_} foreach keys(%data); } #################### package B; use vars qw(@ISA %DATA); @ISA=qw(A); %DATA=('name' => 'B name'); 1;

    --ZZamboni

Re: opinions on the best way to inherit class data
by jplindstrom (Monsignor) on Aug 23, 2000 at 06:46 UTC
    therein lies its downfall - a subclass is obligated to provide its own implementation of Class_Data if it wants to expose its own version of $_Class_Data. not an ideal solution when you want a subclass to provide its own $_Class_Data without requiring the subclass author to provide an implementation.

    I don't quite see the problem. I mean, if a subclass actually want's to provide it's own data, why is it a bad thing that the subclass itself is responsible for making it happen? To me, that seems kind of natural. So, what am I missing? :)

    /J