单身角色在驼鹿

时间:2010-06-17 19:10:34

标签: perl singleton moose

我正在尝试使用Perl和Moose编写单例角色。我知道MooseX :: Singleton模块是可用的,但是当我们的项目需要另一个CPAN模块时总是存在阻力。在尝试这个并遇到一点麻烦后,我想了解为什么我的方法不起作用。我写的单身人士角色如下:

package Singleton;
use Moose::Role;

my $_singleInstance;

around 'new' => sub {
    my $orig = shift;
    my $class = shift;
    if (not defined $_singleInstance ){
        $_singleInstance = $class->$orig(@_);
    }
    return $_singleInstance;
};

sub getInstance
{
    return __PACKAGE__->new();
}

1;

当只有一个类使用单例角色时,这似乎可以找到。但是,当两个类(例如ClassA和ClassB)都使用Singleton角色时,它们都会引用共享的$ _singleInstance变量。如果我调用ClassA-> getInstance,它将返回对ClassA对象的引用。如果我稍后在同一个脚本中调用ClassB-> getInstance,它将返回对ClassA类型的对象的引用(即使我明确地为ClassB调用了getInstance方法)。如果我不使用角色并实际将代码从Singleton角色复制并粘贴到ClassA和ClassB中,它似乎工作正常。怎么回事?

4 个答案:

答案 0 :(得分:4)

您要在所有类型中保存实例,而不是为每个类类型使用不同的实例。

这需要Factory设计模式,例如:

package MyApp::Factory;

my %instances;

# intantiates an object instance if there is none available,
# otherwise returns an existing one.
sub instance
{
    my ($class, $type, @options) = @_;

    return $instances{$type} if $instances{$type};
    $instances{$type} = $type->new(@options);
}

如果你真的想要单身,请安装MooseX :: Singleton而不是自己动手 - 如果你看一下你会看到它会占很多边缘情况。但是,我建议不要强迫你的班级成为单身人士,因为这样可以消除对班级本身的控制。相反,使用工厂(如上所述),因此调用者可以决定如何构造类,而不是强迫所有使用者进入一个用例。

答案 1 :(得分:4)

您的$_singleInstance的词汇范围是显示的块,在本例中是整个Singleton包。您的around修饰符会对此变量形成一个闭包,这意味着每次运行时它都会看到相同的 $_singleInstance,无论它是由哪个类组成的。

解决这个问题的一个简单方法是将单例存储在哈希中:

my %_instances;

around 'new' => sub {
    my $orig = shift;
    my $class = shift;
    if (not defined $_instances{$class} ){
        $_instances{$class} = $class->$orig(@_);
    }
    return $_instances{$class};
};

可能更好的方法是设置一个自定义元类角色,为每个使用该角色的类存储单例实例。

答案 2 :(得分:2)

“我了解MooseX :: Singleton模块是可用的,但是当我们的项目需要另一个CPAN模块时总会有阻力。”

这确实是需要解决的问题。作为dep,MX:Singleton非常小。有什么问题?您是否在共享服务器或类似服务器上停留在全局共享的Perl上?如果是这样,你真的应该看看local :: lib,它旨在让个别开发人员可以使用Makefile.PL脚本轻松管理CPAN依赖关系,就像任何其他CPAN模块一样。

答案 3 :(得分:1)

他们正在共享实例变量。您需要使用角色在包内分配它。

# find storage for instance
my $iref = \${ "${class}::_instance" };

# an instance already exists; return it instead of creating a new one
return $$iref if defined $$iref;

# no instance yet, create a new one
...