Я пытаюсь записать одноэлементную роль с помощью Perl и Американского лося. Я понимаю 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 (даже при том, что я ясно назвал getInstance метод для ClassB). Если я не использую роль и на самом деле копирую и вставляю код от роли Singleton в ClassA и ClassB, это, кажется, хорошо работает. Что продолжается здесь?
Они совместно используют переменную экземпляра. Вам нужно выделить ее внутри пакета, используя роль.
# 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
...
"Я понимаю, что модуль MooseX::Singleton доступен, но всегда есть сопротивление, когда требуется другой модуль CPAN для нашего проекта. "
Это действительно то, что необходимо решить. Как деп, MX:Singleton очень мал. В чем проблема? Вы застряли на глобально разделяемом Perl на общем сервере или подобном? Если да, то вам действительно стоит обратить внимание на local::lib, который разработан для того, чтобы облегчить отдельным разработчикам правильное управление зависимостями CPAN с помощью сценария Makefile.PL, как и любой другой модуль CPAN.
Ваш $_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};
};
Возможно, лучшим способом было бы создать пользовательскую роль метакласса, которая хранит экземпляр синглтона для каждого класса, потребляющего эту роль.
Вы сохраняете экземпляр для всех типов, вместо того, чтобы использовать отдельный экземпляр для каждого типа класса.
Это требует паттерна проектирования 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, а не создавайте свой собственный - если вы посмотрите на исходный текст, то увидите, что он учитывает множество крайних случаев. Однако я бы не советовал заставлять ваши классы быть синглтонами, так как это лишает класс контроля. Вместо этого используйте фабрику (как описано выше), чтобы вызывающая сторона могла решать, как построить класс, а не заставлять всех потребителей использовать его в одном сценарии.