Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

Point me in the right direction with OO inheritance and static variables

by Amblikai (Beadle)
on Sep 02, 2014 at 09:54 UTC ( #1099240=perlquestion: print w/ replies, xml ) Need Help??
Amblikai has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks, still getting to grips with OO Perl. I know there are modules out there like Moose et al, but i' writing classic perl Objects as a learning experience.

I'm not sure where to begin with inheritance though. I was hoping someone could point me in the right direction?

I'd like to have a class which has some static attributes. For example if i create an object it will be created with hardcoded attributes without me passing them as arguments to the "new" constructor. How do i go about that?

Then i have a second class which i will be creating objects which can only have attributes which are found in the previous class. I don't know how to get the second class to know it can only have those attributes and to only allow the object to be called with those attributes having variables assigned to them.

Any help greatly appreciated.

Comment on Point me in the right direction with OO inheritance and static variables
Re: Point me in the right direction with OO inheritance and static variables
by choroba (Abbot) on Sep 02, 2014 at 10:01 UTC
    In the plain OO approach, just specify the default attributes in the constructor:
    sub new { my $class = shift; my %params = ( name => 'John', surname => 'Doe', ); my %args = (%params, @_); # Overwrite the defaults bless \%args, $class }

    To call the constructor of the parent class, use SUPER:

    sub new { my $class = shift; my $self = $class->SUPER::new(%args); return $self }
    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      Thanks! How does the child object "know" which class is the parent? (I hope my terminology is correct)

      In other words, when i'm creating an object in the class which is inheriting the attributes, how does it know where SUPER points?

      Thanks again for your help!

        In the child class, you specified
        use parent 'Parent::Class';

        right? See parent.

        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

        The child class has a package variable called @ISA which tells it the parent class. This is an array because multiple inheritance is supported.

        package Child { require Parent; our @ISA = "Parent"; }

        For some reason, setting @ISA directly like that is not very fashionable, and a lot of people prefer to use a module like base, parent, or superclass to do so. Using one of those modules ensures that @ISA gets set at compile-time rather than run-time, though in practice this feature of them is almost never important.

Re: Point me in the right direction with OO inheritance and static variables
by tobyink (Abbot) on Sep 02, 2014 at 10:19 UTC

    Generally speaking your best bet is to use something like Moose's concept of defaults/builders. Here's an example of how you can implement something very similar using only core modules:

    use v5.14; use warnings; package Horse { # A little bit of metadata about the class. The constructor # uses this to find out what attributes the class supports. sub _attributes { my $class = shift; return ( qw( name colour ) ); } # A fairly standard constructor, similar to MooseX::StrictConstruc +tor sub new { my $class = shift; my $self = bless {}, $class; my %params = @_==1 ? %{$_[0]} : @_; $self->{$_} = delete($params{$_}) for $class->_attributes; die "Unknown parameter(s)... @{[ sort keys %params ]}" if keys %params; return $self; } # Accessors for our attributes, with lazy defaults sub name { $_[0]->{name} //= $_[0]->_build_name } sub colour { $_[0]->{colour} //= $_[0]->_build_colour } # Here are the default values for the attributes sub _build_name { "Cloppity" } sub _build_colour { "brown" } # This is for debugging use JSON::PP; sub dump { my $self = shift; my $class = ref $self; print JSON::PP->new->pretty(1)->canonical(1)->encode({ __CLASS__ => $class, map(+($_ => $self->$_), $class->_attributes), }); } } my $ed = Horse->new( name => "Mr Ed" ); $ed->dump; package Unicorn { use parent -norequire, "Horse"; # Add "horn" to the list of supported attributes. sub _attributes { my $class = shift; return ( $class->SUPER::_attributes(@_), qw( horn ), ); } # Accessor and default value for "horn" attribute. sub horn { $_[0]->{horn} //= $_[0]->_build_horn } sub _build_horn { "medium" } # override the default colour from Horse sub _build_colour { "lavender" } } my $ts = Unicorn->new( name => "Twilight Sparkle" ); $ts->dump; # Note that this dies because of the typo! my $pp = Unicorn->new( naem => "Pinkie Pie" );
      # ... my $ts = Unicorn->new( name => "Twilight Sparkle" ); $ts->dump; # Note that this dies because of the typo! my $pp = Unicorn->new( naem => "Pinkie Pie" );

      I wholeheartedly approve.

      Wow! Thanks! This is pretty much exactly what i'm thinking of with some minor modification (i wish my program was about Unicorns called Twilight Sparkle!!

      I'm having trouble following your subs "name", "colour" etc though.

      Could you explain those? I'm guessing that if i attempt to get the value of "name" before i've assigned it a value, it'll call the _build_X sub and so return a default value. But i'm not entirely sure how it does it? Also i've never seen the "//=" operator before.

      Thanks!

        I've written them in quite a terse fashion. This is because when you're doing OO without any OO frameworks, you end up having to write a lot of these sort of little accessor subs, and making them as abbreviated as possible keeps you sane. Writing out the name sub in full might be:

        sub name { my $self = shift; if (not defined $self->{name}) { $self->{name} = $self->_build_name(); } return $self->{name}; }

        But if you're writing similar accessors for dozens of different attributes, it's nice to abbreviate them so they fit on a line each:

        sub name { $_[0]->{name} //= $_[0]->_build_name } sub colour { $_[0]->{colour} //= $_[0]->_build_colour } sub owner { $_[0]->{owner} } # this one has no default sub height { $_[0]->{height} //= 2.5 } # another way to provide a def +ault ...;

        Slight diversion...

        The disadvantage of the second way of doing defaults (shown above) is it makes the default harder to override when you create a subclass. If the height had been defaulted via $_[0]->_build_height then when we decided to write a Pony::Shetland class, we could simply override _build_height to return a different default value (maybe 1.2?). But with the default 2.5 hard-coded into the height sub itself, we need to override height in Pony::Shetland.

        Obviously, overriding the height sub in Pony::Shetland is perfectly possible. It's technically no more difficult than overriding _build_height. However, overriding _build_height rather than height seems preferable because OO code tends to be more maintainable when you're only overriding very small targeted bits of functionality.

        As an example, let's assume that Pony::Shetland overrides height from Horse. Now somebody goes and releases a new version of Horse with a brand new feature. It allows:

        my $aj = Horse->new(name => "Applejack"); my $metres = $aj->height( in => "metres" ); my $inches = $aj->height( in => "inches" ); my $hands = $aj->height( in => "hands" ); my $silly = $aj->height( in => "lightyears" );

        Nice piece of new functionality, eh? However, Pony::Shetland overrides height, so the new functionality doesn't work there! There's something called the Liskov substitution principle that says anything that works with the base class should work with subclasses. So we've broken that principle.

        If Pony::Shetland was just overriding _build_height, we would never have gotten ourselves into this quandary. The new height would still work in Pony::Shetland.

        End of slight diversion!

        Regarding //=... the // and //= operators were introduced in Perl 5.10. // is much the same as || but rather than testing the truthiness of the left hand value, it tests the definedness. The number 0 and the empty string are defined but false, so if you wanted to be able to have horses with a name "0", this distinction could be important.

        $foo //= $bar is shorthand for $foo = ($foo // $bar), so it means the same as if (not defined $foo) { $foo = $bar }.

Re: Point me in the right direction with OO inheritance and static variables
by Anonymous Monk on Sep 02, 2014 at 10:52 UTC
    One important thing to realize is that in Perl, only methods are inherited. In other languages it's different, but Perl has no notion of 'object attributes', strictly speaking. Any reference can be an object, including references to numbers and strings (and these things can't have user-defined attributes).
    I'm not sure where to begin with inheritance though. I was hoping someone could point me in the right direction?
    A package in Perl can have a magic array @ISA. This array can hold strings, which must be names of packages (class and package is the same thing). When you call a method on an object, like $some_object->some_method() Perl checks which class (package) this $some_object belongs to (you specified it with function bless). If that class doesn't have some_method, then Perl checks the @ISA array of the class. It takes the first string from this array, assumes it's a package name and then searches that package for some_method. If some_method is found, Perl calls some_method, with $some_object as the first argument. So, $some_object->some_method() is equivalent to SomeClass::some_method($some_object) (SomeClass comes from an @ISA array). And that is OOP in Perl.
      and these things can't have user-defined attributes
      Unless inside out.
      لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

        BrowserUK came up with an inside-in storage technique using blessed references to strings.

        Tends to be a little slow for attribute access, but the objects take up very little memory, which is nice.

        a href=
Re: Point me in the right direction with OO inheritance and static variables
by 2teez (Priest) on Sep 02, 2014 at 11:15 UTC

    hi Amblikai,
    If what choroba and tobyink gave are too strong a drink to take all at once, you can check this soft milk

    use warnings; use strict; package Animal; sub new { my $class = shift; my $self = { 'name' => shift || 'Ologbo', 'tail' => shift || 'Yes', }; return bless $self, $class; } package Cat; use base 'Animal'; sub nameMe { my $self = shift; return $self->{'name'}; } package main; my $cat = Animal->new; print $cat->{'name'}, $/; # Ologbo my $cat2 = Cat->new('Blackky'); print $cat2->nameMe # Blackky

    If you tell me, I'll forget.
    If you show me, I'll remember.
    if you involve me, I'll understand.
    --- Author unknown to me

      Thanks, the way you are doing defaults is a bit simpler and might be sufficient for my program. I'm learning this stuff though so anything is good to see! Cheers!

      Hi 2teez,

      I'm not really happy with your suggestion for a newcomer. Why?

      1) Choroba presented a simple constructor (as strong as pure water to stay with your metaphor) where you can can overwrite the default values of name and tail independently in a way most people would expect working with constructors in Perl. The positional parameters to new you present is not very common IMHO. So, I think Corion's constructor is really simple enough.

      2) In package main you then do a

      print $cat->{'name'}, $/;

      which means you circumvent encapsulation (yes, I know, for debugging purposes). You show that to a newcommer to whom should be taught the "right" thing.

      3) The point above IMHO comes only from the fact that you define the method nameMe in the child class. Why haven't you defined it in the base class? Then you wouldn't have to break encapsulation in your example and you would have shown inheritance in a nice mini example.

      Be sure, I wouldn't have written this comment when Amblikai wouldn't have been so "excited" about your example. Please consider my comment as a hopefully constructive criticism.

      And yes, Tobyink's answer is a hard drink to newcomers, but worth studying.

      Best regards
      McA

Re: Point me in the right direction with OO inheritance and static variables
by sundialsvc4 (Monsignor) on Sep 02, 2014 at 22:58 UTC

    To be quite honest about this ... I look at points-of-view such as

    I know there are modules out there like Moose et al, but I'm writing classic perl Objects as a learning experience.
    with the very-pragmatic point of view:   “but, why?”

    Quite frankly, I would embrace tools like Moose (and its various hand-me-downs like Moo and M), “with reckless abandon,” instead of banging my personal forehead against the overhanging-beams that any of them had previously encountered ...

      sundialsvc4 asks:

      but, why?

      The answer's in the part you already quoted:

      "as a learning experience"

      It is good to learn about stuff. What other justification is necessary?

Re: Point me in the right direction with OO inheritance and static variables
by GrandFather (Cardinal) on Sep 03, 2014 at 01:14 UTC

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1099240]
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (6)
As of 2014-09-17 06:02 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (60 votes), past polls