I am using Perl with Moose, and have to prevent instantiation of an abstract class.
The project is in a quite advanced stage - too late for Moose::Role
or MooseX::*
.
I am thinking about checking a package name against a class name in BUILDARGS
,
and calling die
if there's a match.
Is there any problems with this approach?
package Foo::Abstract {
use Moose;
has 'test' => ( isa => 'Int', is => 'rw', default => '0' );
around BUILDARGS => sub {
die if $_[1] eq __PACKAGE__;
$orig = shift;
$class = shift;
$class->$orig( @_ );
};
no Moose;
}
package Foo::Concrete {
use Moose;
extends 'Foo::Abstract';
no Moose;
}
use Test::More;
use Test::Exception;
dies_ok { Foo::Abstract->new() } "cannot instantiate. OK";
my $c;
lives_ok { $c = Foo::Concrete->new() } "instantiated Foo::Concrete. OK";
ok( 0 == $c->test );
done_testing();
As several people have pointed out in comments you probably should be using a Role and making the change in every "subclass" to do composition. However you make a compelling argument for laziness (one change in one place during a refactor).
My suggestion would be "do both". Refactor the existing class you want to be abstract out into a role:
mv lib/Foo/Abstract.pm lib/Foo/Role/Interface.pm;
perl -pie's/\bFoo::Abstract\b/Foo::Role::Interface/g' !$
Then in a new Foo::Abstract
simply do:
package Foo::Abstract;
use Moose;
with qw(Foo::Role::Interface);
around BUILDARGS => sub {
$_[1] ne __PACKAGE__ ? shift->(@_) : die __PACKAGE__ . 'is ABSTRACT';
}
1;
This way you can slowly replace the extends qw(Foo::Abstract)
over time with the more appropriate with qw(Foo::Role::Interface)
but don't have to bite that cost all up front. You can even document that this is the plan in Foo::Abstract
so that other developers who come along help with the conversion.