http://qs1969.pair.com?node_id=1146731


in reply to Behavior of 'our' variables in the package-namespace

When writing OO classes, try to stay away from initialising globals in the outer class definition. Doing that requires (as you have seen) funky work arounds, BEGIN blocks and the like. Its better to encapsulate your packages more clearly, such as the following:

use strict; use warnings; my $foobar_o = Foo::Bar->new( name => "object o" ); printf "%s\n", $foobar_o->text(1); # -- # Package # -- package Foo::Bar; # Package namespace 'our' globals (which must be forward-declared.) our $data; # Fairly minimal constructor, passed an optional "name" argument: sub new { my $class = shift; $class = ref($class) || $class; # subclass boilerplate. my %params = @_; initialise(); my $self = bless {}, $class; foreach my $key (keys %params) { my $func = lc $key; next unless $self->can("$func"); $self->$func($params{$key}); } return $self; } sub initialise { return if defined $data; $data = { 1 => "one", 2 => "two", 100 => "one hundred", }; } sub name { my $self = shift; my $value = shift; $self->{name} = $value if defined $value; return $self->{name} // "<unnamed object>"; } # Silly example method that returns the "text" keyed by %$data. sub text { my $self = shift; my $query = shift or return ''; # Sanity check, making sure the package namespace $data exists: die "Uh-oh, data is not defined!" unless defined $data; my $s = sprintf( "%s: %s is %s", $self->name(), $query, $data->{$query} // "<query value not defined>" ); return $s; } # Explicit RC to support require() and use(). # This is only useful when the package is a real module file. 1;
Notice how the inclusion of an initialise() function gets rid of the BEGIN block, useful in avoiding paying the piper if you don't actually create any objects of this class for whatever reason. Also notice that the use of an accessor shifts all of the code for that accessor to one clearly defined location, so if you decide to change anything about that piece of data it won't affect anything else (this becomes a lot more important when you start having very complex objects with complex rules defining how items are updated and default values).

I also like using named parameters for the constructor, even when only passing one or two parameters. It makes inheritance and future expansion of the class a lot simpler. Combining that with using accessors to get and set values makes the constructor incredibly simple.

Replies are listed 'Best First'.
Re^2: Behavior of 'our' variables in the package-namespace
by Apero (Scribe) on Nov 04, 2015 at 11:54 UTC

    Notice how the inclusion of an initialise() function gets rid of the BEGIN block, useful in avoiding paying the piper if you don't actually create any objects of this class for whatever reason

    I like the potential flexibility there, although I'll also (for the sake of other readers and devil's advocacy) point out that if a caller wanted to put off overhead of loading a module, it's best done in a require() call in whatever conditional requires that module.

    Simply calling require() causes Perl to do extra work (searching the @INC path for a matching module, reading it in, and so on) can be overhead a consuming codebase can avoid if it's well-written. Of course, if the design requires it know in advance the support is there, or the program cannot function without the library, it's best to take the performance hit upfront.

    Not to say efficiency is a bad thing, but I also try to avoid premature optimization, or saving a library/package consumer from their own bad design ;). Where I really like your suggestion is when a package has the potential to do some relatively expensive setup that's only required in certain cases. Maybe a huge amount of data needs to be parsed to do a particular task, yet callers might just want to do a more trivial task that the package supports. Here I think your point about delaying such expensive initialization could have great benefits.


    Also notice that the use of an accessor shifts all of the code for that accessor to one clearly defined location, ...

    Yup, I appreciate the reminder, but the proof-of-concept was intentionally terse to limit the scope. I'm definitely an advocate of accessors, ideally with similar (or at least well-documented) syntax/design to the methods used to set those same object settings/values.


    I also like using named parameters for the constructor, even when only passing one or two parameters. It makes inheritance and future expansion of the class a lot simpler.

    That's a good point. I've often stuck with a single value when I had no intention of expanding a constructor, but that breaks down when the design does change in the future; being friendly to subclassing, possibly by other authors, is a great reason to consider named key/value hashes even in the simple case.

      I like the potential flexibility there, although I'll also (for the sake of other readers and devil's advocacy) point out that if a caller wanted to put off overhead of loading a module, it's best done in a require() call in whatever conditional requires that module.

      Actually, all you're putting off is running the import (which in an OO class you aren't likely to be running anyway). All of the code sitting at the class level will still run:

      package Foo; use strict; use warnings; BEGIN { print "BEGIN called\n"; } print "Class level logic\n"; sub new { my $class = shift; return bless {}, $class; } 1;
      In the above both print statements run, regardless of whether you use or require the package.

      Not to say efficiency is a bad thing, but I also try to avoid premature optimization

      Hmm, maybe I shouldn't have mentioned the additional side-benefits. Its not an attempt at optimisation, its merely a side-benefit of the design. At the end of the day though, its really just a matter of style. If you find it easier to put your initialisation logic at the class level, then thats where it should go. More important than how things should be done is consistency in your code-base :)

        Actually, all you're putting off is running the import (which in an OO class you aren't likely to be running anyway). All of the code sitting at the class level will still run ...

        Not when the require() happens inside a conditional in the library consumer, which is what I was getting at. There's no point (and in fact it can't work) to call require() or use() for packages provided inline, so I presume both of us aren't talking about that case.

        Consider the following structure shown below. The entire Foo:: package (and in fact the module file itself) is only loaded if the conditional is hit. This can be demonstrated by running an strace(1) (or your system's equivalent) against a call to that program both without, and then with the "load" parameter. This parameter in fact causes the module to be conditionally loaded, including the BEGIN code defined in the module.

        File: consumer.pl

        use strict; use warnings; use lib 'lib'; my $arg = shift // ''; if ($arg eq "load") { print "Now loading optional library.\n"; require Foo; my $o = Foo->new; # Do stuff with $o here.. } else { print "I skipped loading the optional library.\n"; print "Try passing the 'load' option to load it.\n"; }

        File: lib/Foo.pm

        package Foo; use strict; use warnings; BEGIN { printf "BEGIN called from package %s\n", __PACKAGE__; } print "Class level logic.\n"; sub new { my $class = shift; $class = ref($class) || $class; # subclass boilerplate. print "constructor invoked for $class\n"; return bless {}, $class; } 1;