1

I've just begun designing a Perl class, and my only prior experience with OOP is with C++, a long time ago.

There are a few items of data that I need to be "class variables" - shared by all instances. I'd like for them to be initialized prior to the first time I instantiate an object, and I'd like for the main program that issues use MyClass to be able to provide a parameter for that initialization process.

Here's a working example of a class with a class variable:

package MyClass;
use strict;
use warnings;

# class variable ('our' for package visibility)                                                                 
#                                                                                                               
our $class_variable = 3;  # Would like to bind to a variable                                                    

sub new {
     my $class = shift;
     my $self = { };
     bless $self, $class;
     return $self;
}

sub method {
    my $self = shift;
    print "class_variable: $class_variable\n";
    ++$class_variable; # prove that other instances will see this change                                        
}

And here's a demo:

#!/usr/bin/perl                                                                                                 

use strict;
use warnings;
use MyClass;

my $foo = MyClass->new();
$foo->method(); # show the class variable, and increment it.

my $bar = MyClass->new();
$bar->method(); # this will show the incremented class variable.

Is there any way for the main program to specify a value for $class_variable? The value would be known at compile time in the main program.

Chap
  • 3,649
  • 2
  • 46
  • 84
  • Possible duplicate of [How can I set a static variable that can be accessed by all subclasses of the same base class (Perl/Moose)?](https://stackoverflow.com/questions/7587157/how-can-i-set-a-static-variable-that-can-be-accessed-by-all-subclasses-of-the-sa) – Evan Carroll Nov 10 '17 at 07:07

4 Answers4

6

You can also make the variable "private" by declaring it with my instead of our. In such a case, you have to provide a class method to initialize it:

my $class_variable = 3;

sub initialize_variable {
    my ($class, $value) = @_;
    die "Ivalid value $value.\n" unless $value =~ /^[0-9]+$/;
    $class_variable = $value;
}

And then in the programme:

'MyClass'->initialize_variable(42);
choroba
  • 231,213
  • 25
  • 204
  • 289
  • That looks good. I don't suppose there's any way to utilize the 'use' statement itself, is there? e.g. `use MyClass qw/42/;` ? – Chap May 20 '13 at 20:31
  • 1
    @Chap If MyClass has a sub called `import`, this will be called when perl encounters the `use` statement with these arguments: `import("MyClass", 42)` (see [import](http://perldoc.perl.org/functions/import.html), [use](http://perldoc.perl.org/functions/use.html)) – Oktalist May 20 '13 at 21:07
  • @Oktalist Beautiful. That's actually the best solution for my purposes, because it ensures that it happens before any object instantiation. If you want to enter that as an answer, I'll give you credit for "right answer." – Chap May 21 '13 at 00:43
  • OK, thanks. I prefer choroba's answer unless you _really_ want that behaviour, though. – Oktalist May 21 '13 at 02:14
  • @choroba You don't need the quotes around `'MyClass'` when you call `initialize_variable`. – Oktalist May 21 '13 at 02:14
  • 1
    @Oktalist: Unless there is a subroutine `MyClass`: Try `sub MyClass::new { bless {}, shift } sub MyClass { die } print MyClass->new` . – choroba May 21 '13 at 06:59
2
$MyClass::class_variable = "some value";
mpapec
  • 50,217
  • 8
  • 67
  • 127
2

Using the import facility:

package MyClass;

my $class_variable;

sub import
{
  (undef, my $new_class_variable) = @_;

  if (defined $class_variable and
      defined $new_class_variable and
      $class_variable ne $new_class_variable)
  {
    warn '$MyClass::class_variable redefined';
  }

  $class_variable = $new_class_variable if defined $new_class_variable;
}

Pass the value when you use the module:

use MyClass qw(42);

It's not exactly idiomatic Perl, but it's not uncommon either. That sanity check in the middle of the function should give you a hint about why it might not be the best approach in all cases. If MyClass is only supposed to be used from a top-level script, you could enforce that sanity check instead:

caller eq 'main' or die 'MyClass can only be used from package main';
Oktalist
  • 14,336
  • 3
  • 43
  • 63
  • I see your point about the possibility of also being used from another package. In my case, the argument I wish to pass is the name of the program, so that MyClass can read some program-specific metadata from a database. Definitely a case where it only makes sense to use it from 'main'. (I think.) – Chap May 21 '13 at 04:30
  • @Chap can't you use [$0](http://search.cpan.org/~rjbs/perl-5.18.0/pod/perlvar.pod#$0)? – Oktalist May 21 '13 at 13:39
  • The argument refers, more precisely, to the application protocol that will be used. Although there's currently a one-to-one relationship between programs and protocols, I don't want to insist on it. (If that makes sense.) – Chap May 21 '13 at 16:39
0

You can also use a Class method:

Ex:

package myclass;

our $class_variable = 5;

sub myclass_method{

    my ($class, $new_class_variable_value) = @_;

    if( $class_variable != $new_class_variable_value )
    {
        ## set the new value of the class/package variable
        $class_variable = $new_class_variable_value;
    }
}

In your script you'd invoke this by doing:

myclass::myclass_method('myclass', 7);