7

我正在尝试使用 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

4 回答 4

5

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

这需要工厂设计模式,例如:

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 而不是自己安装——如果你查看源代码,你会发现它占了很多边缘情况。但是,我建议不要将您的课程强制为单例,因为这会将控制权从课程本身中移除。相反,使用工厂(如上),因此调用者可以决定如何构造类,而不是强制所有消费者进入一个用例。

于 2010-06-17T19:48:51.053 回答
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};
};

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

于 2010-06-17T19:49:20.407 回答
3

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

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

于 2010-06-17T19:58:53.497 回答
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
...
于 2010-06-17T19:48:55.790 回答