Beefy Boxes and Bandwidth Generously Provided by pair Networks kudra
Welcome to the Monastery
 
PerlMonks  

Avoiding global object handle

by Biker (Priest)
on Mar 26, 2002 at 09:44 UTC ( #154358=perlquestion: print w/ replies, xml ) Need Help??
Biker has asked for the wisdom of the Perl Monks concerning the following question:

So, I do create numerous inhouse business specific Perl modules. (I.e. not CPAN candidates, too specific for that.)

I find that a whole bunch of my modules are primarily built for providing code reuse between applications, not to provide code reuse within an application.

First choice is to use OO style modules or traditional functional style modules. I personally prefer using the OO style modules even for these kind of modules. There are several reasons for this, including consistent function call/method execution techniques, the question of name space pollution, etc. So, the choice has fallen on OO style modules in all cases concerned for this post.

As the modules, written as OO modules, are written for reuse between applications there is no logical reason to instantiate more than one object of the class within the application using the module. At the moment of instantiation an object handle is retrieved and stored.

my $handle=Module->new(); # Very classical, no surprices here.

Now the question is how to store this handle. The handle will be used between many, the majority, of the subs in the application. But not all subs will need this object handle.

I see two classical ways of handling the handle. (No pun intended.)
  1. Store the handle as a global variable, accessible by all subs. (Potentially with use vars qw/$handle/; )
  2. Provide the handle as a parameter to all subs that will need it.
The problem with the first solution is that I have to maintain a global variable, something that I try to avoid, mainly because I dislike the usage of global variables. They tend to confuse me. And style, of course.

The second solution means putting the handle on the call stack for almost all subs. Performance hit? Maybe, since these are apps that do a lot. And I mean a lot.
It will also imply that a number of subs will take the handle as a parameter with the only objective to provide it to another sub, called from this sub. Not very clean at all.

I came up with the following approach:
use strict; use warnings; use Module; BEGIN { my $handle; sub get_handle { # Simplified for clarity. # If handle defined, return it. # Otherwise create it and return it. defined($handle)|| defined($handle=Module->new())|| die("Failed to instantiate a Module object\n"); $handle; } } get_handle()->method_a('parameter'); get_handle()->method_b('parameter'); exit;

Now, the result from this is that I have just hidden a global variable inside a sub. I have created a sub to get to my global object handle.

I don't use vars. I don't have a global variable in it's traditional meaning. Technically, my global variable is implemented as a function call.

All subs are available throughout the application eq 'good approach'.
No global variables are available throughout the application eq 'good approach'.

  • Am I cheating myself?
  • Will other developers understand what I'm trying to achieve?
  • Am I really achieving something?
  • Is this overkill?
  • How do you handle these situations?
  • Thoughts?



Everything went worng, just as foreseen.

Comment on Avoiding global object handle
Select or Download Code
Re: Avoiding global object handle
by Smylers (Pilgrim) on Mar 26, 2002 at 10:30 UTC

    You dismissed method 2 as possibly being too much of a performance hit, since it involves passing a scalar variable containing a reference into each subroutine.

    But your preferred method involves calling an extra subroutine every time you want to access your object. I haven't done any tests, but I'd be surprised if sticking an extra param in an existing sub call is more costly than making a whole new call. Especially if in a particular sub you do ten method calls on your object — you've now replaced one param with ten function calls.

    Did you actually have a problem with your code running too slow, or did you just get the urge to do this because you suspected there might be one?

      If I have to call the get_handle() when I need it, I can appreciate that there's a performance cost. But pulling this handle through numerous calls to subs that don't need the handle themself, with the only reason to provide the handle to a 'sub-sub' feels as paying for something without getting anything in return.

      Furthermore, imagine three subs named x(), y() and z(). sub x() calls sub y() that calls sub z().

      One day sub z() is changed and needs access to the handle. As a result, the programmer must change sub x() and sub y() to take the handle as a parameter even though they couldn't care less about the handle themself.

      This would lead to discussions like: "If we want to change sub z() to use the functionality of Module.pm, then we must first do an impact analyze to determine how many other subs must be enhanced to provide the handle, directly or indirectly, to our sub z()".

      If sub z() can get a direct access to the handle, the change can be implemented in sub z() with no impact on other subs.

      Yes, I'm talking about rather big and complex Perl applications here.


      Everything went worng, just as foreseen.

      "Especially if in a particular sub you do ten method calls on your object — you've now replaced one param with ten function calls."

      Not necessarily. If I know that I'm going to use the object 10 times in a specific sub, I can call get_handle() once and save the handle in my own variable throughout the sub.

      sub z { my $handle=get_handle(); $handle->method_a('parameter'); $handle->method_b('parameter'); $handle->method_c('parameter'); # Etc. }



      Everything went worng, just as foreseen.

Re: Avoiding global object handle
by jeffenstein (Scribe) on Mar 26, 2002 at 12:03 UTC

    You could also take a look at Class::Singleton. Possibly you could subclass this to be a repository for handles. For instance, (assuming $rep is an instance of a subclass of Class::Singleton) add a handle using $rep->add_handle("Module1", Module1->new() );, then call $rep->get_handle("Module1") to retrieve the handle later. Of course, you'll need to write these methods yourself.

    That way, you only need to add use Rep; our $rep = Rep->new(); into the modules that will need to access any of the handles.

    It has the same effect of having globals, but just seems to me a bit cleaner, along with less names of globals running around.

Re: Avoiding global object handle
by Jammer (Novice) on Mar 26, 2002 at 12:41 UTC
    According to "Design Patterns" (Gamma/Helm/Johnson/Vlissides), what you describe is a Singleton, as is your implementation of it.
    This is the OO "correct" (whatever that means ;) way of ensuring a class has only one instance, and of providing a global point of access to it.
    I usually call the get_handle() method get_instance() when I do this, so I know what it is when I come back to it later.
Re: Avoiding global object handle
by Biker (Priest) on Mar 26, 2002 at 13:05 UTC

    As per suggestion above, I have read up on Singleton. This proved to be very interesting reading indeed.

    "The instance() method is used to create a new Class::Singleton instance, or return a reference to an existing instance. Using this method, it is only possible to have a single instance of the class in any system."

    But it is not the solution to my situation. I want the object to exist in one instance per application within the system. Not one instance per system.


    Everything went worng, just as foreseen.

      Some bad wording in the documentation. It's actually one instance per process.

Re: Avoiding global object handle
by mamboking (Monk) on Mar 26, 2002 at 13:21 UTC
    If the code that needs the handle is in a class, then you could pass the handle in on instantiation and save it as an attribute of the object. It would then be available to all of the methods of the class.
Re: Avoiding global object handle
by Anonymous Monk on Mar 26, 2002 at 13:25 UTC
    I don't quite see the point in having a global function instead of a global variable. To me it makes more sense to have a global variable since that needs to be declared. If you have a subroutine that you think you can remove you won't find out that it perhaps was needed until the program dies because the subroutine was missing in some obscure corner of the program. However, you'll instantly know if the variable is missing. Furthermore, if you use our() you'll get a warning if you don't use the variable. Example:
    sub make_handle { ... } our $handle = make_handle(); # "... used only once"
    So I consider globals a good thing when they're proper to use. One can argue that using a variable like this (and still keep the 'used only once' warning) will require you to always setup the handle. But if it's a large application and the handle will almost always be used then I see no reason not to initialize it right away.

    I also believe it's faster to use a variable than to perform a sub call. But if you still want to use a sub call and really really care about performance, you might want to try a hack like this:
    sub foo { my $foo = shift; return undef unless defined $foo; no warnings 'redefine'; *foo = sub { $foo }; foo(); } { my $bar; sub bar { defined $bar ? $bar : ($bar = shift); } }
    &foo and &bar are analogous. But &foo runs faster, at least according my benchmark.

    Cheers,
    -Anomo
        I don't quite see the point in having a global function instead of a global variable. To me it makes more sense to have a global variable since that needs to be declared.

      Accessing the global handle through a subroutine makes it much harder to accidentally clobber it. (Further, it makes clear to anyone reading your code that $handle has a well-defined value that doesn't change, which is kind of nice for maintenance programmers.)

      In general, I'm a big fan of wrapping related scope-specific variables in a singleton class, just because it makes domain control so much easier. This works best in very specific cases, where you don't see much potential for partial code reuse, because you're coupling all the code that depends on the singleton. It becomes correspondingly harder to pull out individual functions from that scope and use them elsewhere, because you have to pull out the singleton as well (which probably doesn't fit your new problem perfectly). In Biker's situation, this might be a good fit.

      --
      :wq

        I'm speaking, of course, of when it's best fit to use a variable/subroutine instead of some other technique.

        Accessing the global handle through a subroutine makes it much harder to accidentally clobber it.

        True. I didn't think of that, especially since he uses it as foo()->bar, and it's hard to modify it that way. I don't think there's a high risk of accidently modifying this particular variable, but I see your objection.

        (Further, it makes clear to anyone reading your code that $handle has a well-defined value that doesn't change, which is kind of nice for maintenance programmers.)

        By convention you name those variables in upper-case, so in this case it would be $HANDLE instead. There should be no larger question marks regarding that issue.

        I think that the small risk of clobbering the variable is out-matched by the warning feature of variables. It helps to keep code clean, and that is something maintainers also appreciate. I hate seeing unused subs/variables just because the author was too afraid/paranoid to remove them.

        Cheers,
        -Anomo
      I don't quite see the point in having a global function instead of a global variable

      One thing it does buy you is the ability to drop a breakpoint on it in the debugger, and then play with caller to see where you were called from. That is not quite as simple to do with a variable.

      In a farfetched scenario, you could also diddle the package's symbol table to wrap or replace the function itself. That's much harder to do with a variable.


      print@_{sort keys %_},$/if%_=split//,'= & *a?b:e\f/h^h!j+n,o@o;r$s-t%t#u'
Re: Avoiding global object handle
by gregorovius (Friar) on Mar 26, 2002 at 18:15 UTC
    I would go for the global variable, but hidden behind a module which will not export it by default. You could call this variable a "framework global", and is also a singleton.
    package MyFramework; use strict; use Exporter; use vars qw(@ISA @EXPORT @EXPORT_OK); @ISA = qw(Exporter); @EXPORT_OK = qw( $HANDLE ); 1;
    Thus, whenever you need to use it in a module, you'll just have to put:
    use MyFramework qw ( $HANDLE );
    at the top of it and it'll be available for all methods in that module. Where would you initialize this variable? As early as possible, before any method requiring it is executed, preferrably in a BEGIN block within the MyFramework package itself.

    This approach is equivalent to your "get_handle" method because it ensures that the same instance will be used by all. It also makes it easy to replace the class that produces the handle, because it would only be invoked once in the whole of your code, at the place where $HANDLE is initialized.

Re: Avoiding global object handle
by extremely (Priest) on Mar 26, 2002 at 22:21 UTC
    You know, there is a lot of good ideas posted already, but in your situation, I'd probably just pass around a hash full of objects. You could probably tie the hash to a meta_new that tries to create your object if it doesn't already exist, I suppose.

    Whenever I catch myself passing a bunch of the same arguments to myriad subs, I stuff em into a hash and just pass it around.

    --
    $you = new YOU;
    honk() if $you->love(perl)

•Re: Avoiding global object handle
by merlyn (Sage) on Mar 26, 2002 at 22:29 UTC
    As the modules, written as OO modules, are written for reuse between applications there is no logical reason to instantiate more than one object of the class within the application using the module. At the moment of instantiation an object handle is retrieved and stored.
    Then you don't really have instances any more. You have a singleton. And a package is a fine singleton.

    Don't make the same mistake as our favorite whipping boy, CGI.pm. If you're always going to have at most one of a thing in an application, use class methods, not instances methods, and lexical variables for member variables. Fast. Easy. And subclassable, if done right.

    -- Randal L. Schwartz, Perl hacker

      I see your point about classwide methods on a package being a singleton, the class itself being like a single instance with lexical member variables.

      In practice, though, I have found that classwide methods make it hard to replace functionality by subclassing, because one needs to hardcode the package name everywhere its methods are called. Then, when subclassing the singleton package, one needs to replace all those calls to instead use the name of the subclass. Do you know if there's a way around this?

      I think this is a shortcoming of using classwide methods. Where I work we now instead favor having Factory classes for producing singleton variables that we make available as "framework globals" as described in my earlier posting. This Factory class reads a configuration file in order to determine what kind of subclass it should instantiate, which works great for plug-and-play configuration of different installs of our application. Example:

      $FOO = FooFactory->get_foo();
      As a convention we use all-caps for singleton variables.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://154358]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others imbibing at the Monastery: (7)
As of 2014-04-16 11:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (424 votes), past polls