2010-06-17 15 views
5

Je tente d'écrire un rôle singleton en utilisant Perl et Moose. Je comprends qu'un module MooseX :: Singleton est disponible mais il y a toujours de la résistance quand on demande un autre module CPAN pour notre projet. Après avoir essayé cela et avoir un peu de mal je voudrais comprendre pourquoi ma méthode ne fonctionne pas. Le rôle singleton je l'ai écrit est la suivante:Singleton Roles in Moose

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; 

Cela semble fonctionner à trouver quand une seule classe utilise le rôle singleton. Toutefois, lorsque deux classes (ClassA et ClassB par exemple) utilisent le rôle Singleton, elles apparaissent toutes deux faisant référence à une variable $ _singleInstance partagée. Si j'appelle ClassA-> getInstance, il renvoie une référence à un objet ClassA. Si j'appelle ClassB-> getInstance plus tard dans le même script, il renvoie une référence à un objet de type ClassA (même si j'ai clairement appelé la méthode getInstance pour ClassB). Si je n'utilise pas de rôle et copiez et collez le code du rôle Singleton dans ClassA et ClassB, cela semble fonctionner correctement. Que se passe t-il ici?

+1

Vous réalisez que l'emballage «nouveau» demande simplement un monde de blessures, n'est-ce pas? – Ether

Répondre

3

Vous sauvegardez l'instance sur tous les types, plutôt que d'en utiliser une différente pour chaque type de classe.

Cela nécessite le modèle de conception d'usine, par exemple:

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

Si vous voulez vraiment singletons, s'il vous plaît installer MooseX :: Singleton plutôt que de rouler votre propre - si vous regardez la source que vous verrez Cela représente beaucoup de cas limites. Cependant, je vous déconseillerais de forcer vos classes à être singletons, car cela enlève le contrôle de la classe elle-même. Au lieu de cela, utilisez une fabrique (comme ci-dessus), de sorte que l'appelant peut décider comment construire la classe, plutôt que de forcer tous les consommateurs dans une seule utilisation.

1

Ils partagent la variable d'instance. Vous devez l'allouer dans le package à l'aide du rôle.

# 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 
... 
+2

Si vous allez suivre cette route, l'utilisation d'un rôle de métaclasse est probablement beaucoup plus robuste que monkeying avec la table de symboles. – friedo

3

Votre $_singleInstance est scope au lexicalement bloc où il apparaît, dans ce cas, l'ensemble de votre paquet Singleton. Votre modificateur around forme une fermeture sur cette variable, ce qui signifie qu'il voit le le même$_singleInstance chaque fois qu'il est exécuté, quelle que soit la classe dans laquelle il est composé.

Une façon simple de résoudre ce serait de stocker vos singletons dans un hachage:

my %_instances; 

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

Peut-être une meilleure façon serait de mettre en place un rôle de métaclasse personnalisé qui stocke l'instance singleton pour chaque classe qui consume ce rôle.

+0

J'ai pensé que c'était un problème avec la variable étant portée au rôle.C'était juste un peu déroutant parce que __PACKAGE__ semble se référer au paquet consommant le rôle, et pas au paquet/rôle "Sngleton". J'avais espéré que n'importe quelles variables définies dans un rôle seraient étendues au paquet les consommant et pas au paquet du rôle. – mjn12

+1

Moose affecte la façon dont Perl analyse ou comprend les variables. Il n'y a pas de moyen (facile) de faire en sorte que le comportement que vous supposez ici fonctionne, et serait de toute façon hors de portée de Moose. – perigrin

2

« Je comprends un module MooseX :: Singleton est disponible, mais il y a toujours une résistance lorsqu'il est nécessaire d'un autre module CPAN pour notre projet. »

C'est vraiment quelque chose qui doit être pris en compte. En tant que dep, MX: Singleton est très petit. Quelle est la solution? Êtes-vous coincé sur un Perl global partagé sur un serveur partagé ou similaire? Si c'est le cas, vous devriez vraiment regarder local :: lib, qui est conçu pour permettre aux développeurs individuels de gérer facilement les dépendances CPAN avec un script Makefile.PL, comme n'importe quel autre module CPAN.

+1

Merci d'avoir pris le temps de répondre à la question, mais ce n'est vraiment pas utile. J'ai demandé de l'aide pour comprendre pourquoi ce code particulier ne fonctionnait pas ET j'ai reconnu l'existence de MooseX :: singleton pour éviter ce type de réponse. – mjn12