zaven has asked for the wisdom of the Perl Monks concerning the following question:

Oh Holiest of Perlish Truth Seekers:

I have created a hierarchy of classes in OO style for a testing program. At the top of the hierarchy is the TestSuite class, which creates several other types of objects, and these new objects will each create several others, etc. Everything is ultimately spawned by the TestSuite, since it is created first. However, most objects do not have an IS-A relationship with TestSuite.

My question is this: if I have a hash of globally useful information in a TestSuite instance object, how can I make this hash accessible by any object that happens to be created by a TestSuite, without passing it as a method parameter to every new object created by TestSuite? Or, if it is already accessible, how do I properly access it from one of the secondary or tertiary objects? For example:

my $ts = new TestSuite(); # $ts->{info} is now defined $ts->make_a_blob(); # $ts->{blob} is now a ref to an object of type Blob $ts->print_blob() # Calls $self->{blob}->print_stuff(); # I want the method Blob::print_stuff() to access information in $ts-> +{info} #I want to avoid doing this: $ts->{blob}->print_stuff( $ts->{info} );
In other words, what's the best way for Blob::print_stuff() to access $ts->{info} if we assume Blob::print_stuff() is always called from a method of TestSuite? much thanks

Replies are listed 'Best First'.
Re: Proper way to create 'globals'
by kyle (Abbot) on Feb 07, 2007 at 21:54 UTC

    If your TestSuite is a singleton (as it sounds like), you can make a package variable ($TestSuite::ts) that contains the one TestSuite that's been instantiated, and then everybody can have at it as they like.

    If you plan to have more than one TestSuite, have it hand itself to its sub-objects at their creation.

    $ts->make_a_blob(); $ts->{blob}->{your_lord_and_master} = $ts; weaken $ts->{blob}->{your_lord_and_master};

    Note the importance of using weaken from Scalar::Util to create a weak reference. Without that, the circular references we just created will keep the objects from being destroyed properly when it's time to collect garbage.

    Then Blob::print_stuff() can look at $self->{your_lord_and_master}->{info} and more.

      Thanks folks, I think the package variable is probably the correct answer here :-)
Re: Proper way to create 'globals'
by Tanktalus (Canon) on Feb 07, 2007 at 21:09 UTC

    A few possibilities. Simplest is just to make $ts a global, even if that becomes $TestSuite::ts. The painful part here is just to keep ts pointing to the right object at all times if there can be more than one TestSuite object active in the process at a time. Not hard, just tedious.

    Next is to pass in $ts, not $ts->{info}. Because although print_stuff needs access to $ts->{info}, frobnicator may need access to $ts->{frobs}. A consistent framework where the $ts is always passed in may suffice.

    Finally, your factory method (where you create the objects) could just take the test suite object as a parameter, and put it into the object being created such that $ts->{blob}->{testsuite} == $ts. Then in print_stuff, you'd be able to use $self->{testsuite}->{info}.

    That said, I wouldn't actually suggest accessing other objects' internals directly like that. Make accessor methods to do it. Just nicer that way.

Re: Proper way to create 'globals'
by chromatic (Archbishop) on Feb 08, 2007 at 00:32 UTC

    Globals suck. Make a method which returns that data. Zing, now it's accessible to all subclasses, it's encapsulated behind a nice interface, and it's not stompable elsewhere by accident.

      Your reply does not help at all. Maybe read the question again?

      Hint: Just replace every "$ts->{info}" in the question with "$ts->info()" while reading...


      Search, Ask, Know

        I can read just fine, thank you. I said "globals suck". Perhaps I should have been more explicit to say "passing parameters is much better, and I think you'll appreciate that design more in the future if you have to make further changes."

Re: Proper way to create 'globals'
by adrianh (Chancellor) on Feb 08, 2007 at 12:33 UTC
    I have created a hierarchy of classes in OO style for a testing program

    Not an answer to your question... and I'm obviously biased... but have you looked at Test::Class?

Re: Proper way to create 'globals'
by Moron (Curate) on Feb 08, 2007 at 10:41 UTC
    TestSuite is a container class so it might be easier for the top of the individual test class hierarchy to be something else e.g. Test, and for that to have all the methods that get inherited down the tree.

    -M

    Free your mind

Re: Proper way to create 'globals'
by dragonchild (Archbishop) on Feb 09, 2007 at 01:47 UTC
    You have two solutions to your problem. Neither is preferable to the other - both work equally well.
    • Catalyst and Excel::Template both solve this problem by passing the context around to every method. In E::T, this is an actual Excel::Template::Context object. In Catalyst, this is a Catalyst::Controller object. So, every method in both starts as so:
      sub floober { my ($self, $c) = @_; }
    • DBM::Deep prefers to provide a reference to the context in the instantiation of every object, then weaken it. So, every object can call $self->engine and get the context object. Thus, every constructor looks like:
      sub new { my $class = shift; my ($params) = @_; my $self = bless {}, $class; $self->{engine} = $params->{engine}; Scalar::Util::weaken( $self->{engine} ); return $self; }
    As I'm the author of both Excel::Template and DBM::Deep, I feel that each is useful in different situations.

    BUT ... you really need to use one of them. Messing around with globals is bad juju. That you're asking here is a sign that you feel it smells wrong. That's the sign of a software journeyman. To demonstrate mastery, you need to learn why it smells and how to avoid it.


    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Proper way to create 'globals'
by bsdz (Friar) on Feb 08, 2007 at 18:35 UTC
    At the risk of losing the respect of many OO programmers ;) Why not use PadWalker to peek around other classes' lexical scopes. I.e.
    use strict; package TestSuite; sub new { my ($this, $motto) = @_; bless { info => $motto }, $this; } sub make_a_blob { my ($self) = @_; $self->{blob} = Blob->new; return $self; } sub print_blob { my ($self) = @_; $self->{blob}->print_stuff(); } package Blob; use PadWalker qw(peek_my); sub new { bless {}, shift; } sub print_stuff { my $callerObject = ${peek_my(1)->{'$self'}}; print $callerObject->{info}."\n"; } package main; my $ts = new TestSuite("The holy grail!"); my $ts2 = new TestSuite("A rotten apple!"); $ts->make_a_blob(); $ts->print_blob(); $ts2->make_a_blob->print_blob; __DATA__ The holy grail! A rotten apple!
    Update: Changed code a little but idea is still the same.