Dirk80 has asked for the wisdom of the Perl Monks concerning the following question:
I know that there are many systems like Moo, Moose, etc. to build classes. But at the moment I'm reading the old book "Object Oriented Perl" from Damian Conway. Although it is very old, it helps me great to understand Object oriented Perl.
In his book there is an example with a derived class. It is ok for me, but the derived classes have to inherit a lot. I show that to you in Variant 1.
package Variant1::RootClass; our $VERSION = 1.00; use strict; use warnings; use Carp; use vars qw( $AUTOLOAD ); { # Encapsulated class data my %_attr_data = # DEFAULT ACCESSIBILITY ( '_root_entry1' => [ undef , 'read/write'], '_root_entry2' => [ undef , 'read'] ); # Class methods to operate on encapsulated class data # Is a specified object attribute accessible in a given mode sub _accessible { my ($self, $attr, $mode) = @_; $_attr_data{$attr}[1] =~ /$mode/; } # Classwide default value for a specified object attribute sub _default_for { my ($self, $attr) = @_; $_attr_data{$attr}[0]; } # List of names of all specified object attributes sub _standard_keys { keys %_attr_data; } } # Constructor may be called as a class method or object method sub new { my ($caller, %arg) = @_; my $caller_is_obj = ref($caller); my $class = $caller_is_obj || $caller; my $self = bless {}, $ class; foreach my $attr_name ( $self->_standard_keys() ) { my ($arg_name) = ($attr_name =~ /^_(.*)/); # take value from given argument if available if( exists $arg{$arg_name} ) { $self->{$attr_name} = $arg{$arg_name}; } # take value from object if caller is an object elsif( $caller_is_obj ) { $self->{$attr_name} = $caller->{$attr_name}; } # take default value else { $self->{$attr_name} = $self->_default_for($attr_name); } } return $self; } sub DESTROY { # nothing to do here } # Implement get_... and set_... methods sub AUTOLOAD { no strict "refs"; my ($self, $newval) = @_; # Was it a get_... method? if( $AUTOLOAD =~ /.*::get(_\w+)/ && $self->_accessible($1, 'read') + ) { my $attr_name = $1; *{$AUTOLOAD} = sub { return $_[0]->{$attr_name} }; return $self->{$attr_name}; } # Was it a set_... method? if( $AUTOLOAD =~ /.*::set(_\w+)/ && $self->_accessible($1, 'write' +) ) { my $attr_name = $1; *{$AUTOLOAD} = sub { $_[0]->{$attr_name} = $_[1]; return; }; $self->{$1} = $newval; return; } # Must have a mistake then ... croak "No such method: $AUTOLOAD"; } 1;
use Variant1::RootClass; package Variant1::DerivedClass; our @ISA = qw( Variant1::RootClass ); our $VERSION = 1.00; use strict; use warnings; { # Encapsulated class data my %_attr_data = # DEFAULT ACCESSIBILITY ( '_derived_entry1' => [ undef , 'read'], '_derived_entry2' => [ undef , 'read'], '_derived_entry3' => [ undef , 'read/write']); # Class methods to operate on encapsulated class data # Is a specified object attribute accessible in a given mode sub _accessible { my ($self, $attr, $mode) = @_; return $_attr_data{$attr}[1] =~ /$mode/ if exists $_attr_data{ +$attr}; return $self->SUPER::_accessible($attr,$mode); } # Classwide default value for a specified object attribute sub _default_for { my ($self, $attr) = @_; return $_attr_data{$attr}[0] if exists $_attr_data{$attr}; return $self->SUPER::_default_for($attr); } # List of names of all specified object attributes sub _standard_keys { my ($self) = @_; (keys %_attr_data, $self->SUPER::_standard_keys()); } } 1;
use Variant1::DerivedClass; package Variant1::DerivedDerivedClass; our @ISA = qw( Variant1::DerivedClass ); our $VERSION = 1.00; use strict; use warnings; { # Encapsulated class data my %_attr_data = # DEFAULT ACCESSIBILI +TY ( '_derived_derived_entry1' => [ undef , 'read'], '_derived_derived_entry2' => [ undef , 'read'], '_derived_derived_entry3' => [ undef , 'read/writ +e']); # Class methods to operate on encapsulated class data # Is a specified object attribute accessible in a given mode sub _accessible { my ($self, $attr, $mode) = @_; return $_attr_data{$attr}[1] =~ /$mode/ if exists $_attr_data{ +$attr}; return $self->SUPER::_accessible($attr,$mode); } # Classwide default value for a specified object attribute sub _default_for { my ($self, $attr) = @_; return $_attr_data{$attr}[0] if exists $_attr_data{$attr}; return $self->SUPER::_default_for($attr); } # List of names of all specified object attributes sub _standard_keys { my ($self) = @_; (keys %_attr_data, $self->SUPER::_standard_keys()); } } 1;
In this variant 1 the derived classes have to reimplement _accessible, _default_for and _standard_keys. But they can inherit the constructor new and the AUTOLOAD subroutines.
I now created a variant 2. It has the same behavior from outside the class. But here the derived classes only need to reimplement _get_attr_data. All other subroutines can be inherited.
package Variant2::RootClass; our $VERSION = 1.00; use strict; use warnings; use Carp; use vars qw( $AUTOLOAD ); { # Encapsulated class data my %_attr_data = # DEFAULT ACCESSIBILITY ( '_root_entry1' => [ undef , 'read/write'], '_root_entry2' => [ undef , 'read'] ); # Class methods to operate on encapsulated class data # get attribute class data sub _get_attr_data { %_attr_data; } # Is a specified object attribute accessible in a given mode sub _accessible { my ($self, $attr, $mode) = @_; my %attr_data = $self->_get_attr_data(); $attr_data{$attr}[1] =~ /$mode/; } # Classwide default value for a specified object attribute sub _default_for { my ($self, $attr) = @_; my %attr_data = $self->_get_attr_data(); $attr_data{$attr}[0]; } # List of names of all specified object attributes sub _standard_keys { my ($self) = @_; my %attr_data = $self->_get_attr_data(); keys %attr_data; } } # Constructor may be called as a class method or object method sub new { my ($caller, %arg) = @_; my $caller_is_obj = ref($caller); my $class = $caller_is_obj || $caller; my $self = bless {}, $ class; foreach my $attr_name ( $self->_standard_keys() ) { my ($arg_name) = ($attr_name =~ /^_(.*)/); # take value from given argument if available if( exists $arg{$arg_name} ) { $self->{$attr_name} = $arg{$arg_name}; } # take value from object if caller is an object elsif( $caller_is_obj ) { $self->{$attr_name} = $caller->{$attr_name}; } # take default value else { $self->{$attr_name} = $self->_default_for($attr_name); } } return $self; } sub DESTROY { # nothing to do here } # Implement get_... and set_... methods sub AUTOLOAD { no strict "refs"; my ($self, $newval) = @_; # Was it a get_... method? if( $AUTOLOAD =~ /.*::get(_\w+)/ && $self->_accessible($1, 'read') + ) { my $attr_name = $1; *{$AUTOLOAD} = sub { return $_[0]->{$attr_name} }; return $self->{$attr_name}; } # Was it a set_... method? if( $AUTOLOAD =~ /.*::set(_\w+)/ && $self->_accessible($1, 'write' +) ) { my $attr_name = $1; *{$AUTOLOAD} = sub { $_[0]->{$attr_name} = $_[1]; return; }; $self->{$1} = $newval; return; } # Must have a mistake then ... croak "No such method: $AUTOLOAD"; } 1;
use Variant2::RootClass; package Variant2::DerivedClass; our @ISA = qw( Variant2::RootClass ); our $VERSION = 1.00; use strict; use warnings; { # Encapsulated class data my %_attr_data = # DEFAULT ACCESSIBILITY ( '_derived_entry1' => [ undef , 'read'], '_derived_entry2' => [ undef , 'read'], '_derived_entry3' => [ undef , 'read/write']); # Class methods to operate on encapsulated class data # get attribute class data of this class and its base class sub _get_attr_data { (%_attr_data, $_[0]->SUPER::_get_attr_data()); } } 1;
use Variant2::DerivedClass; package Variant2::DerivedDerivedClass; our @ISA = qw( Variant2::DerivedClass ); our $VERSION = 1.00; use strict; use warnings; { # Encapsulated class data my %_attr_data = # DEFAULT ACCESSIBILI +TY ( '_derived_derived_entry1' => [ undef , 'read'], '_derived_derived_entry2' => [ undef , 'read'], '_derived_derived_entry3' => [ undef , 'read/writ +e']); # Class methods to operate on encapsulated class data # get attribute class data of this class and its base class sub _get_attr_data { (%_attr_data, $_[0]->SUPER::_get_attr_data()); } } 1;
And here a test script to use these classes.
#!/usr/bin/perl use strict; use warnings; use Data::Dumper; use Variant1::DerivedDerivedClass; use Variant2::DerivedDerivedClass; # VARIANT 1 my $obj1 = Variant1::DerivedDerivedClass->new('root_entry1' => 1, 'root_entry2' => 2, 'derived_entry1' => 3, 'derived_entry2' => 4, 'derived_entry3' => 5, 'derived_derived_entry1' +=> 6, 'derived_derived_entry2' +=> 7, 'derived_derived_entry3' +=> 8 ); print $obj1->set_derived_entry3(27); print Dumper $obj1; # VARIANT 2 my $obj2 = Variant2::DerivedDerivedClass->new('root_entry1' => 1, 'root_entry2' => 2, 'derived_entry1' => 3, 'derived_entry2' => 4, 'derived_entry3' => 5, 'derived_derived_entry1' +=> 6, 'derived_derived_entry2' +=> 7, 'derived_derived_entry3' +=> 8 ); print $obj2->set_derived_entry3(27); print Dumper $obj2;
Would it be possible to even put the _get_attr_data only in the base class. In this way the derived classes would have to reimplement nothing. Everything could be derived.
If not. I'm not sure if I implemented the variant 2 in a good way. For my feeling I'm doing it in a bad way because I'm calling the _get_attr_data at so many places. Is there a better way to implement it?
Thanks a lot for your help.
2019-12-16 Athanasius added readmore tags.
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Class attribute handling
by choroba (Cardinal) on Dec 11, 2019 at 16:19 UTC | |
|
Re: Class attribute handling
by tobyink (Canon) on Dec 11, 2019 at 17:14 UTC | |
by Dirk80 (Pilgrim) on Dec 12, 2019 at 15:33 UTC | |
by tobyink (Canon) on Dec 12, 2019 at 23:14 UTC | |
by Dirk80 (Pilgrim) on Dec 13, 2019 at 17:03 UTC | |
|
Re: Class attribute handling
by NERDVANA (Priest) on Dec 11, 2019 at 21:16 UTC |