Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Re^3: OO manner of accessing static variables in a subclass?

by kcott (Archbishop)
on Aug 10, 2016 at 22:51 UTC ( [id://1169535]=note: print w/replies, xml ) Need Help??


in reply to Re^2: OO manner of accessing static variables in a subclass?
in thread OO manner of accessing static variables in a subclass?

Firstly, a few words about some OO relationships. From your usage of terms like "parent", "child" and "subclass", I'm not convinced that you've fully grasped their meanings in the OO context.

Inheritance

This refers to what's called an IS-A relationship. For instance, in English, a Sports Car is a Car.

Equivalently, in Perl, the class SportsCar is a subclass of Car; SportsCar inherits from Car; Car is a parent class of SportsCar; SportsCar is a child class of Car; and @SportsCar::ISA should have Car as one of its elements.

Composition

This refers to what's called a HAS-A relationship. For instance, in English, a Car has an Engine; a Car is composed of a number of components one of which is an Engine; a Car is incomplete, and will not function as intended, without an Engine; however, an Engine is not a Car.

Equivalently, in Perl, the class Car needs to load the class Engine to function normally; however, the class Engine is NOT a subclass of Car; Engine DOES NOT inherit from Car; Car is NOT a parent class of Engine; Engine is NOT a child class of Car; and @Engine::ISA should NOT have Car as one of its elements.

Aggregation

This is also a HAS-A relationship but, unlike Composition, it is optional rather than essential. For instance, in English, a Car might have a Radio; a Car can have optional components one of which could be a Radio; a Car is complete, and will function as intended, without a Radio; as with an Engine, a Radio is not a Car.

Equivalently, in Perl, the class Car might conditionally load the class Radio; although, it is not required to function normally; the class Radio is NOT a subclass of Car; Radio DOES NOT inherit from Car; Car is NOT a parent class of Radio; Radio is NOT a child class of Car; and @Radio::ISA should NOT have Car as one of its elements.

Now let's change your original class names to more meaningful ones; perhaps Taxidermist and Taxidermy::Data. It should be fairly obvious that Taxidermy::Data is not a Taxidermist: there's no IS-A relationship here. However, a Taxidermist will have Taxidermy::Data; this is a HAS-A relationship and, as such, there are no parent, child or subclasses involved.

Going back to your original names, and refactoring the code to completely decouple Monks from Monks::Data, here's how you might implement a HAS-A relationship. (Again, the code is simplistic just to show a technique; production code should include validation, error checking and so on.)

In the script (pm_1169455_oo_class_example.pl), I read the name of the data module directly from the command line. This could also be done via options, config files, etc. I've only changed one line:

my $obj = Monks::->new;

to

my $obj = Monks::->new(data => $ARGV[0]);

The module Monks::Data is completely unchanged.

I've created another data module Monks::DataWeird, to test the decoupling, which looks like this:

package Monks::DataWeird; use strict; use warnings; our $VERSION = '20160808.00'; sub get_furries { [qw{Rabbits Alligators Minks}] } sub get_texture_for { +{qw{Rabbits soft Minks supersoft Cats scales}} +} 1;

The Monks module no longer contains any reference to Monks::Data or, indeed, any Monks::* module. It now looks like this:

package Monks; use strict; use warnings; our $VERSION = '0.01'; sub new { my ($class, @args) = @_; my $self = bless { @args } => $class; $self->_init; return $self; } sub _init { my ($self) = @_; eval "require $_[0]->{data}"; } sub _get_data { $_[0]->{data} } sub is_furry { my ($self, $furry) = @_; grep { /$furry/ } @{$self->_get_data->get_furries}; } sub find_fur_texture { my ($self, $fur) = @_; $self->_get_data->get_texture_for->{$fur}; } 1;

Here's three sample runs: two with the real data modules and one with a bogus, non-existent module.

$ pm_1169455_oo_class_example.pl Monks::Data Cats: furry? Yes Alligators: furry? No Rabbits: furry? Yes Fur texture for Cats: coarse
$ pm_1169455_oo_class_example.pl Monks::DataWeird Cats: furry? No Alligators: furry? Yes Rabbits: furry? Yes Fur texture for Cats: scales
$ pm_1169455_oo_class_example.pl XMonks::Data Can't locate object method "get_furries" via package "XMonks::Data" at + Monks.pm line 24.

Just to briefly cover your other points:

"I'm sort of partial to BEGIN blocks, ..."

I have no problem with using BEGIN (or similar) blocks. I didn't use one as I saw no need. See ++hippo's response. Also look at the doco for the our function: it's usage and scoping rules seem to trip up a few users.

"Your guess is correct regarding $self->get_attr vs. $self->{attr}. I personally find the latter a lot easier to use, ..."

See the code for the Monks module. The "latter" form is only used for initialisation (_init()) and the accessor (_get_data()); elsewhere the accessor is used. Do not use the direct access method in other modules or scripts. Although it's usual to bless a hashref for Perl modules, that's not universal by any means: for example, IO::Handle uses a globref and Class::Std uses a scalarref. Even when the object remains a blessed hashref, the accessor could still change: as a contrived example, instead of $self->{attr}, it could become something like $self->{attr}[USER] // $self->{attr}[DEFAULT].

"Sorry about the furry/fluffy thing, I was just trying to have some fun with my sample code. :) "

No need to apologise at all. Changing everything to furry was just expedient for me.

I think my initial comments on "OO relationships", and subsequent refactoring, probably cover your other points.

[Aside: I'm not requesting you make changes, but for future reference take a look at "What shortcuts can I use for linking to other information?" and "Writeup Formatting Tips". Using links (e.g. parent, instead of 'parent') and marking up inline code (e.g. $self->{attr}, instead of $self->{attr}) can improve readability.]

— Ken

Replies are listed 'Best First'.
Re^4: OO manner of accessing static variables in a subclass?
by HipDeep (Acolyte) on Aug 11, 2016 at 05:42 UTC

    Thanks again Ken, as I was obviously using the terms incorrectly I appreciate the primer. Would the terms "main" module and "data" module be more appropriate? My intention would be for the user to load the main module, and never have to access the data module directly. So it sounds like a composition relationship (as the data module has things in it that the main module needs to do its work), so NEEDS-A would be a better description for my situation, but I see where you're going with Car::Engine.

    In regards to using methods to access data vs. accessing the data directly, I see what you're saying in regards to the examples I've given already, but I have another case that I'm still confused (or maybe just stubborn) about how to handle. My module has two modes of operation. You can either pass it the name of a thing which it uses to look up data related to that thing, and then parse it; or you can pass it a file that contains the data already looked up, and then it parses that. So the constructor can look like this:

    my $object = Foo::->new ( thing => "$ARGV[0]" );
    or this:
    my $object = Foo::->new ( file => "$ARGV[0]" );

    Then at various places in the code I do things like this:

    if (not defined $self->{file}) {
    to determine which mode I'm in (this is all within the main module). There are also a couple of options that the user can pass in the constructor, and various other things which it's just easy to stuff into the object which I also access the same way (again, all in the main module).

    Given that it's me doing the access in the module itself, and not a published interface to the data, is that Ok? Or do I need to access that data internally via a method as well? That would seem pretty inefficient to me. I do use the convention of prefixing things which I intend to be "private" with an underscore, so that if the user goes poking around in the code they will at least get a clue that they are messing with something they should not, that is subject to change, etc. But if there is a better way to do that, I'm definitely open to it.

    And your gentle hint about learning the markup is well taken. I will try to pick that up as time goes on.

        I did read the ootut, along with a lot of other tuts. :) I think I need to go back and re-read them now that I've got my hands quite a bit dirtier, and along with yours and the other monks' patient instruction, hopefully more of it will sink in.

        In regards to the code itself, I'm close to finishing the cleanup, after which I will write the documentation, and post for review. At that point what I'm up to should be clear, and hopefully y'all can (gently) let me know about all of the millions of mistakes I'm sure I made. :)

        Meantime, I got the data class cleaned up in the manner you described, and it's working with the main class just fine, so I feel that I've not only accomplished something, but learned some things as well, so thanks again!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://1169535]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others cooling their heels in the Monastery: (4)
As of 2024-04-25 15:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found