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:
- As a method with void prototype:
my $_Class_Data = "private stuff";
...
sub Class_Data () { return $_Class_Data; }
On the surface this appears the best solution: it is
inherited, and may be subclassed; though 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.
- Storing a reference to class data a la tchrist.
my $_Class_Data = "private";
...
# constructor
sub new
{
my $class = shift;
return bless(
{ _Class_Data => \$_Class_Data,
# other init...
},
$class
);
}
This approach works fine if you only want to provide
$_Class_Data only from the class in which $_Class_Data
was first declared, *and* if you provide an accessor
method for it, or note it as "public class data" in the object
hash. If you really want to have each class have its *own*
version of $_Class_Data, you're out of luck.
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".
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. | [reply] [d/l] [select] |
|
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... ;-) | [reply] [d/l] [select] |
|
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.) | [reply] [d/l] |
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 | [reply] |
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") | [reply] [d/l] |
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.
| [reply] [d/l] [select] |
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
| [reply] [d/l] |
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
| [reply] |
|
|