防止 Moose 抽象的实例化 class

Prevent instantiation of a Moose abstract class

我正在将 Perl 与 Moose 一起使用,并且必须防止实例化抽象 class。

该项目处于相当先进的阶段 - 对于 Moose::RoleMooseX::* 来说太晚了。

我正在考虑根据 BUILDARGS 中的 class 名称检查包名称, 如果匹配,则调用 die

这种方法有什么问题吗?

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();

怎么来不及用角色?只需替换:

use Moose;

use Moose::Role;

并在 Foo::Concrete 中替换

extends 'Foo::Abstract';

with 'Foo::Abstract';

正如一些人在评论中指出的那样,您可能 应该 使用角色并在每个 "subclass" 中进行更改以进行组合。但是,您为懒惰提出了令人信服的论据(重构期间在一个地方进行了一次更改)。

我的建议是 "do both"。重构现有class你想抽象出来的角色:

mv lib/Foo/Abstract.pm lib/Foo/Role/Interface.pm; 
perl -pie's/\bFoo::Abstract\b/Foo::Role::Interface/g' !$

然后在一个新的 Foo::Abstract 中简单地做:

package Foo::Abstract;
use Moose;
with qw(Foo::Role::Interface);
around BUILDARGS => sub { 
    $_[1] ne __PACKAGE__ ? shift->(@_) : die __PACKAGE__ . 'is ABSTRACT';
}
1;

这样您就可以随着时间的推移慢慢地用更合适的 with qw(Foo::Role::Interface) 替换 extends qw(Foo::Abstract),而不必一开始就付出所有代价。您甚至可以记录这是 Foo::Abstract 中的计划,以便其他一起来的开发人员帮助进行转换。