No, I wasn't listening, this is my first time really looking at
PerlMonks. I was in the middle of mucking about with Tie::Cache::LRU
and found myself writing a virtual base class and all these
enforcement methods and such. So rather than make a one-off (heavens,
no!) I slapped together Class::Virtual.
So first off, Class::Virtual *doesn't* require each subclass to call a
check function directly (although re-reading the documentation, I can
see why one would think that. Will correct.) The check methods are
there for code auditing purposes, reporting, internal use and
subclasses of Class::Virtual (as we'll see in a moment). I'll change
the docs to de-emphisize them and emphisize the real purpose of the
module.
The major difference I can see between Class::Virtual and
AbstractClass is C::V does its work at run-time whereas AC works at
compile-time. C::V waits until you actually call an unimplemented
virtual method before it yells. This might seem silly (since it would
have blown up anyway) but it does provide a more informative error.
Also, doing the checks at compile-time would cause half my modules to
blow up (being the poster boy for method auto-generation that I am).
However, not to be outdone...
package Class::Virtually::Abstract;
use base qw(Class::Virtual);
use Carp::Assert;
assert( prototype('CORE::bless') eq '$;$' );
sub bless ($;$) {
my $class = $_[1] || caller;
if( grep { $_ eq 'Class::Virtually::Abstract' } @{$class.'::ISA'}
+) {
confess("Connot bless objects into abstract class $class");
}
else {
CORE::bless(shift, $class);
}
}
sub import {
shift;
my $base = caller();
*{$base.'::bless'} = \&bless;
*{$base.'::import'} = sub {
my $class = shift;
return if $class eq $base;
foreach my $missing_meth ($class->missing_methods) {
require Carp;
Carp::croak("Class $class must define $missing_meth for cl
+ass ".
"$base");
}
$class->SUPER::import(@_);
};
1;
}
That should emulate the compile-time behavior of AbstractClass and its
mucking with bless(). (BTW Your prototype is wrong). And feel free
to steal the closure trick to generate the import() instead of the eval().
I can flesh this out an ship it with Class::Virtual if you'd like.
So, digging into the internals... a few things bother me about
AbstractClass. First, it hijacks import(). Why should an OO module
need an import() routine? Well, AbstractClass needs one. Lots of
hybrid OO/functional modules need it (Class::Fields for one). Its too
important to take away.
Another problem with the import() route is its brittleness. Consider
the following...
package Foo;
require Some::Abstract::Class;
@ISA = qw(Some::Abstract::Class);
Ooops. And this is a perfect valid and common, way of subclassing
which defeats AbstractClass. Consider this, too...
package Foo;
use base qw(Some::Abstract::Class);
base.pm does not call import() (and rightly so). AbstractClass
defeated.
Another, @ABSTRACT_METHODS bothers me. Magical global variables
bother me, doubly so in OO contexts. Triply so when you start putting
this magical global into other packages. Maybe if you put it into
@{$base.'::__ABSTRACT_METHODS'} or something. I take the class data
route instead.
Overriding bless... I haven't decided if that's Evil or not. I'd
rather define a default new() method that does the same thing.
Besides, even if you do manage to generate an object from a virtual
class, its going to explode the first time you use it.
Another, with AbstractClass,
SomeAbstractClass->can("some_virtual_method") returns false. With
Class::Virtual it returns true (but if you try to use the method
returned it will explode). I guess this is a design decision.
I don't totally understand why AbstractClass doesn't work with MI, but
Class::Virtual doesn't have any problem (god forbid, I can't live
without MI).
Finally, in the Class::Virtual scheme, virtual base classes are
subclasses of Class::Virtual. I like this because of its
synchronicity and because it allows the behavior of Class::Virtual to
be altered (as for Class::Virtually::Abstract).
|