我正在尝试使用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中,它似乎工作正常。怎么回事?
答案 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
...