Search code examples
perloopstaticmoose

How can I set a static variable that can be accessed by all subclasses of the same base class (Perl/Moose)?


Since Perl/Moose always calls the base class' BUILD function before it calls the subclass BUILD function, there is a new instance of the base class everytime you instantiate a subclass.

How do I go about creating a static variable that can be used by all the subclasses, or alternatively how can I create a static base or abstract class? (does that approach even make sense?)

I'm trying to create a variable that dynamically enables or disables certain features of a function defined at run-time in the base class but accessible from the sub classes.

So if I do something like

my obj = My::childObject_1->new( 'use_my_var' => 1 );

it will also be true for

my obj2 = My::childObject_2->new();
my obj3 = My::childObject_3->new();

without having to specifically define that variable. Unless

my obj4 = My::childObject_2->new( use_my_var' => 0 ); 

in which case it would from that point be false for all subclasses because they all

extends My::BaseObject

Additionally, is there a design pattern that describes this behavior?

(Note: I'm on a shared system so I can't install MooseX -- or at least I haven't been able to figure out how to setup local PERL5LIB installs of modules in my user directory =/ so Moose-only solution helps for now!)


Solution

  • UPDATE

    Now there is a much better way to do this, use MooseX::ClassAttribute

    Then just use class_has rather than has for the methods you want shared with all instances.

    package My::Class;
    
    use Moose;
    use MooseX::ClassAttribute;
    
    class_has 'Cache' =>
        ( is      => 'rw',
          isa     => 'HashRef',
          default => sub { {} },
        );
    
    __PACKAGE__->meta()->make_immutable();
    

    OLD

    Additionally, is there a design pattern that describes this behavior?

    Yes. It's called a Singleton. A Singleton is a pattern whereby multiple initiations (calls to ->new) will return the same object. You can either do it like this, or store the variable outside of a class. Moose provides a layer that will permit you to create Singletons easily (thought it isn't particularly hard either way): the module MooseX::Singleton. Moose also permits you to delegate to another object by using an accessor.

    Here we use MooseX::Singleton, and delgation to a hidden attribute to achive the desired effect.

    package MySingleton;
    use MooseX::Singleton;
    
    has 'foo' => ( is => 'rw', isa => 'Bool', default => 0 );
    
    package ClassA;
    use Moose;
    
    has '_my_singleton' => (
      isa => 'MySingleton'
      , is => 'ro'
      , default => sub { MySingleton->new }
      , handles => [qw( foo )]
    );
    
    
    package ClassB;
    use Moose;
    
    has '_my_singleton' => (
      isa => 'MySingleton'
      , is => 'ro'
      , default => sub { MySingleton->new }
      , handles => [qw( foo )]
    );
    
    package main;
    use Test::More tests => 5;
    
    my $class_a = ClassA->new;
    my $class_b = ClassA->new;
    
    is( $class_a->foo(0), 0, 'Set A to false' );
    is( $class_a->foo, 0, 'A Is false' );
    is( $class_b->foo, 0, 'B Is false' );
    is( $class_b->foo(1), 1, 'Set B to true' );
    is( $class_a->foo, 1, 'A is true' );
    

    Or, without MooseX

    Please don't do this unless required. The MooseX method is much nicer:

    package Underclass;
    use Moose;
    
    has 'foo' => ( is => 'rw', isa => 'Bool', default => 0 );
    
    package SingletonWrapper;
    my $obj;
    sub new {
        if ( $obj ) { return $obj; }
        else { $obj = Underclass->new }
    }
    
    package ClassA;
    use Moose;
    
    has '_my_singleton' => (
        isa => 'Underclass'
        , is => 'ro'
        , default => sub { SingletonWrapper->new }
        , handles => [qw( foo )]
    );
    
    
    package ClassB;
    use Moose;
    
    has '_my_singleton' => (
        isa => 'Underclass'
        , is => 'ro'
        , default => sub { SingletonWrapper->new }
        , handles => [qw( foo )]
    );