web-dev-qa-db-fra.com

Comment créer des fonctions privées dans un module Perl?

Je travaille sur un petit module Perl et, pour une raison quelconque, le script de pilote de test qui utilisait mon nouveau module appelle l'une des fonctions que je pensais être privée, et le processus a abouti. J'ai été surpris, j'ai donc commencé à chercher sur Google et je ne trouvais pas vraiment de documentation sur la manière de créer des fonctions privées dans les modules Perl ...

J'ai vu un endroit qui disait de mettre un point-virgule après l'accolade fermante de votre fonction "privée", comme ceci:

sub my_private_function {
...
}; 

J'ai essayé cela, mais mon script de pilote pouvait toujours accéder à la fonction que je voulais être privée.

Je vais créer quelque chose qui sera un exemple plus court, mais voici ce que je suis après:

Module TestPrivate.pm:

package TestPrivate;

require 5.004;

use strict;
use warnings;
use Carp;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);

require Exporter;

@ISA = qw(Exporter AutoLoader);

our @EXPORT_OK = qw( public_function );
our @EXPORT    = qw( );

$VERSION = '0.01';

sub new {
    my ( $class, %args ) = @_;
    my $self = {};
    bless( $self, $class );
    $self->private_function("THIS SHOULD BE PRIVATE");
    $self->{public_variable} = "This is public";
    return $self;
}

sub public_function {
    my $self     = shift;
    my $new_text = shift;
    $self->{public_variable} = $new_text;
    print "Public Variable: $self->{public_variable}\n";
    print "Internal Variable: $self->{internal_variable}\n";
}

sub private_function {
    my $self     = shift;
    my $new_text = shift;
    $self->{internal_variable} = $new_text;
}

Pilote: TestPrivateDriver.pl

#!/usr/bin/Perl
use strict;
use TestPrivate 'public_function';
my $foo = new TestPrivate();
$foo->public_function("Changed public variable");
$foo->private_function("I changed your private variable");
$foo->public_function("Changed public variable again");
$foo->{internal_variable} = "Yep, I changed your private variable again!";
$foo->public_function("Changed public variable the last time");

Sortie du pilote:

Public Variable: Changed public variable
Internal Variable: THIS SHOULD BE PRIVATE
Public Variable: Changed public variable again
Internal Variable: I changed your private variable
Public Variable: Changed public variable the last time
Internal Variable: Yep, I changed your private variable again!

J'ai donc ajouté un point-virgule après la dernière accolade de fermeture dans le module, mais le résultat est toujours le même. La seule chose que j'ai vraiment trouvée est d'ajouter cette ligne en tant que première ligne à ma fonction privée:

caller eq __PACKAGE__ or die;

Mais cela semble assez hacky. Je n'ai pas beaucoup d'expérience dans l'écriture de modules Perl, alors peut-être ai-je mal configuré mon module? Est-il possible d'avoir des fonctions privées et des variables dans les modules Perl?

Merci de m'aider à apprendre!

32
BrianH

De perldoc perltoot (environ un quart du document):

Perl n'impose pas de restrictions sur qui utilise quelles méthodes. La distinction public-privé est par convention et non par syntaxe. (Sauf si vous utilisez le module Alias ​​décrit ci-dessous dans "Membres de données en tant que variables".) Il arrive parfois que les noms de méthodes commencent ou se terminent par un trait de soulignement ou deux. Ce marquage est une convention indiquant que les méthodes sont privées de cette classe et parfois de ses connaissances les plus proches, ses sous-classes immédiates. Mais cette distinction n’est pas imposée par Perl lui-même. C'est au programmeur de se comporter.

Par conséquent, je vous recommande de mettre un ou deux traits de soulignement au début de vos méthodes "privées" pour dissuader l'utilisation.

33
jedihawk

Il y a seulement "The Kludge" de stocker une référence de code dans une variable lexicale, ce que personne en dehors de cette portée ne peut voir:

my $priv_func1 = sub { my $self = shift; say 'func1'; };

sub public_sub { 
    my $self = shift;

    $priv_func1->( $self );
}

Et je ne peux pas {penser} d'une manière de créer des champs rigoureusement "protégés".

C'est tout ce que je sais (à part les filtres de source ... chut, je ne les ai pas mentionnés ....)


EDIT: En fait, il s'avère que je peut penser à une façon très désordonnée de faire protéger. Mais cela impliquerait probablement de faire passer tous les appels par le biais de AUTOLOAD sub. (!!)

22
Axeman

Cela marche:

my $priv_func1 = sub {
    my $self = shift; say 'func1';
};

sub public_sub { 
    my $self = shift;

    $self->$priv_func1(@_);
}
14
Leon Timmermans

Il suffit de vérifier l'appelant:

package My;

sub new {
  return bless { }, shift;
}

sub private_func {
  my ($s, %args) = @_;
  die "Error: Private method called"
    unless (caller)[0]->isa( ref($s) );

  warn "OK: Private method called by " . (caller)[0];
}

sub public_func {
  my ($s, %args) = @_;

  $s->private_func();
}

package main;

my $obj = My->new();

# This will succeed:
$obj->public_func( );

# This will fail:
$obj->private_func( );
8
JDrago

Qu'essayez-vous de faire? Peut-être existe-t-il une meilleure façon pour Perl de faire tout ce que vous essayez d'accomplir.

Par exemple, si vous ne voulez pas que les objets fouillent dans vos objets parce que vous souhaitez appliquer l'encapsulation, vous pouvez utiliser quelque chose comme Class :: InsideOut . Ce module a un module de documentation Class :: InsideOut :: About qui explique le concept. Il y a aussi Object :: InsideOut , que Brian Phillips a déjà mentionné.

6
brian d foy

Nous pouvons écrire quelque chose ci-dessous dans la fonction privée Perl pour vérifier si l'appel provient du même objet que caller[0] donne package.

sub foo {
  my ($s, %args) = @_;
  die "Error: Private method called"
      unless (caller)[0]->isa( ref($s) );
}
3
Raj

Ce style de OO commence à se sentir un peu "uni-perlish" après un moment lorsque vous réalisez que vous ne pouvez pas simplement utiliser Data :: Dumper pour vider directement l'objet ou regarder à l'intérieur de l'objet pour le regarder. Les données. Cependant, si vous voulez essayer, je vous recommande d'utiliser Object :: InsideOut . Il prend en charge des données et méthodes privées pour vos objets, ainsi qu'un certain nombre d'autres fonctionnalités utiles (génération d'accesseur, constructeur par défaut, etc.).

3
Brian Phillips

Si vous utilisez un système tel que Moose , vous pouvez obtenir une distinction public/privé comme vu ici .

2
Chris Simmons

Dans le fichier de votre paquet: Définissez les méthodes privées comme CODE-Ref, c'est-à-dire:

my $private_methode = sub{};
0
Rolf