2002_10_04, [EMAIL PROTECTED] wrote:
 > I don't really follow the
:
 > logic used in above


I'm looking at the CGI.pm v2.86 source available at
     http://search.cpan.org/src/LDS/CGI.pm-2.86/CGI.pm


One general strategy for supporting OO and procedural interfaces without 
having to write twice as many subroutines is to start by writing just 
the OO interface, and then build in a 'default object'. This is what 
CGI.pm is doing.

The first argument passed to an object's method ("instance method" not 
"class method") is object reference. If you want that same method to 
work as a 'regular' subroutine for your procedural interface, then you 
need for it to work even if the first argument is *not* an object reference.

So, if you have a method like this:

     sub foo {
         my( $self, @p ) = @_;
         return $self->bar(  join('|', @p)  );
     }

and you want it to work in a functional interface, then you might think 
along these lines...

     sub foo {
         my( $self, @p ) = @_;
         unless (  is_a_good_object_reference( $self )  ) {
             unshift( $self, @p ); # it was an arg, not an obj
             $self = default_obj_for_functional_interface();
         }
         return $self->bar(  join('|', @_)  );
     }

We'll hold off for a moment on describing

     &is_a_good_object_reference()
     and
     &default_obj_for_functional_interface()

because we can see that this is going to make all of our methods much 
messier. We're going to be adding quite a bit of code to each and every 
method in order to have it work via a procedural interface.

So, why don't we replace that with a a subroutine call?

     sub foo {
         my( $self, @p ) = self_or_default(@_);
         return $self->bar(  join('|', @_)  );
     }

Now we can apply that code to all of the methods we want to work in our 
procedural interface and things will be much tidier.

Our &self_or_default() needs to cover the that stuff we removed:

     sub self_or_default {
         my( $self, @p ) = @_;
         unless (  is_a_good_object_reference( $self )  ) {
             unshift( $self, @p ); # it was an arg, not an obj
             $self = default_obj_for_functional_interface();
         }
         return( $self, @p );
     }

We can get rid of a temporary variable by doing this:

     sub self_or_default {
         unless (  is_a_good_object_reference( $_[0] )  ) {
             return( default_obj_for_functional_interface(), @_ );
         }
         return @_;
     }

Hmmm... This will do the job when we *only* call it like we did in 
&foo() above where we want self_or_default to return not only our 
object, but also all the other args. What if we want to be able to call 
it to just return an object? In that case, we would be calling in scalar 
context. We can modify &self_or_default() to accomodate that possible 
use as well:

     sub self_or_default {
         unless (  is_a_good_object_reference( $_[0] )  ) {
             unshift( @_, default_obj_for_functional_interface() );
         }
         return wantarray ? @_ : $_[0];
     }

If called in array context, you get the object (a default object if 
procedural interface was used) and the other args. If called in scalar 
context, you get just the object.

Now, every time we call this, we're going to be getting a fresh default 
object. We can save memory by only creating a default object the first 
time we need it. Then every other procedural call can just use that same 
default object. Let us suppose that we declare a global value somewhere 
which starts off as undefined. We'll put our default object in there.

In CGI.pm, that global is $Q. It starts off undefined when the module is 
loaded and &initialize_globals() gets called during load. Line 109 calls 
that sub, and within that sub line 95 sets $Q to be undefined. We'll 
call our default object $Q to make comparisons with CGI.pm easier.

     sub self_or_default {
         unless (  is_a_good_object_reference( $_[0] )  ) {
             $Q = default_obj_for_functional_interface()
                  unless defined($Q);
             unshift( @_, $Q );
         }
         return wantarray ? @_ : $Q;
     }

In the return line, we could have continued using $_[0], but $Q makes it 
clearer what it is that we're returning in scalar context so we don't 
have to look back to see what we stuffed in $_[0].

Now, a default object for the functional interface can be constructed in 
pretty much the same way you construct a normal object for your OO 
interface. So if your class is FooClass, then creating a default object 
is just this easy:

     sub self_or_default {
         unless (  is_a_good_object_reference( $_[0] )  ) {
             $Q = FooClass->new() unless defined($Q);
             unshift( @_, $Q );
         }
         return wantarray ? @_ : $Q;
     }

This is all fine and good. Everyone is happy. Everyone uses your module 
because they love it so much. Some folks start building other modules 
that in turn use your module. One of those folks sends you a small 
complaint... "I need for the default object to be of *my* Class (which 
builds on yours) but the default object is always FooClass".

How can you help this person?

Well, you can put 'FooClass' in a package variable. If they really want 
to change the behavior, they can reach in and change the value of that 
package variable. This is what CGI.pm does.

On line 142, the package variable $CGI::DefaultClass gets the value of 
'CGI'. In our case, we might instead have $FooClass::DefaultClass get 
the value of 'FooClass'. Users can reach in and adjust that package 
variable if they need to, but 99% of them won't need to. Now our sub 
looks like this:

     sub self_or_default {
         unless (  is_a_good_object_reference( $_[0] )  ) {
             $Q = $FooClass::DefaultClass->new()
                  unless defined($Q);
             unshift( @_, $Q );
         }
         return wantarray ? @_ : $Q;
     }

Yes, you can call a class method on a string. The following are 
equivilant, and give two new objects of the same class:

     my $scalar  = 'FooClass';
     my $object1 = $scalar->new();
     my $object2 = FooClass->new();

We still haven't filled in &is_a_good_object_reference() in the above 
subroutine though. A good object reference will be defined, and will 
have an isa relationship with our class (is an object of our class).

     sub self_or_default {
         unless (   defined( $_[0] )
                 && UNIVERSAL::isa( $_[0], 'FooClass' )
         ) {
             $Q = $FooClass::DefaultClass->new()
                  unless defined($Q);
             unshift( @_, $Q );
         }
         return wantarray ? @_ : $Q;
     }

The isa() call is slightly expensive, and since the most common case is 
that we're dealing with an object of our class, not some subclass. So, 
we can optimize slightly be testing that case first instead of jumping 
right into the whole isa() inheritance search.

     sub self_or_default {
         unless (   defined( $_[0] )
                 && (
                     ref($_[0]) eq 'FooClass'
                     or
                     UNIVERSAL::isa( $_[0], 'FooClass' )
                 )
         ) {
             $Q = $FooClass::DefaultClass->new()
                  unless defined($Q);
             unshift( @_, $Q );
         }
         return wantarray ? @_ : $Q;
     }

Now we've fully fleshed out everything, and we optimze for common cases, 
we work in scalar or array context, and we allow folks to tweak the 
default class if they must. All is good. In fact, this looks very 
similar to what you find in CGI.pm's self_or_default (lines 227..235) 
with one exception. The very first line in the body of CGI.pm's version 
has some oddness going on.

If the first argument is a string 'CGI' then the subroutine immediately 
returns @_. Why? Well, 'CGI' is the package name. When you call a class 
method, the first argument is the package name. So, it looks like LS 
wants to allow some class method to use the &self_or_default() 
subroutine too.

Looking at the pod, the only documented class methods are &new(), 
&compile(), &nph() (I think). None of them call &self_or_default(), but 
&nph() (and lots of other subroutines) call &self_or_CGI() which seems 
to work something like &self_or_default(). I'm taking a stab in the dark 
that line 328 (the first line of self_or_default()) is a start in the 
direction of removing &self_or_CGI() with the (now more generic) 
&self_or_default().

I confess that I'm not entirely clear on what that line is doing. 
However, I hope the above was enough to give you a sense of what the 
&self_or_default() subroutine is all about, and that you can use some of 
the techniques to support combined OO/procedural interfaces with your 
own methods.


 > I want to update an existing module to support
 > an OO and procedural interface. The module will
 > be primarily OO,


This is good. Just write it entirely OO to start with.


 > support for two main functions
 > as a simple procedural interface.


Those two functions can be modified to look at the first argument and 
create a default $self if the first argument doesn't look like a good $self.


 > I don't really follow the
 > $Q = $CGI::DefaultClass->new unless defined($Q);
 > or DefaultClass logic used in above.


The $CGI::DefaultClass is an extra layer that was probably added for a 
special need. You can probably get away with just creating a new object 
of your class the way you would normally do it.


 > Is there a standard approach


I haven't really looked. It does seem like a nice candidate for a 
module, and with all the other OO helper modules out there, someone may 
already have written code to encapsulate and automate the above sorts of 
changes. If so, I'm guessing the module grabs your original method and 
stashes it away, replacing it with a wrapper method that takes care of 
the dual-interface needs and then calls your original method in standard 
OO form. This approach would make it easy to add procedural support to 
existing OO methods, and the cost overhead wouldn't be much more than 
doing the above work manually. However, it might screw up your caller() 
chain. If your methods care about that, then you would probalby want to 
do things manually.


 > I just want to create $self if there isn't one.


Something like the following will probalby do the job for most cases:


     package FooClass;
     my $default_obj;

     # ...........

     sub self_or_default {
         unless (          defined( $_[0] )
                 && UNIVERSAL::isa( $_[0], 'FooClass' )
         ) {
             return(
                 ( $default_obj ||= FooClass->new() ),  @_
             );
         }
         return @_;
     }

     # ...........

     sub foo { # call as object method or as function
         my( $self, @p ) = self_or_default(@_);
         return $self->bar(  join('|', @_)  );
     }

     # ...........


Note that I haven't tried to run any of the code in this email. You may 
find typos or other hazards.

-matt

Reply via email to