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