How do I define private or internal methods in obj

2019-04-06 17:00发布

问题:

I'm using Damian Conway's "inside-out" objects as described is his wonderful book Perl Best Practices to construct an object-oriented interface to a security system at my client. I'm coming across the need to use internal helper methods within my module that I would normally designate as "_some_method". However, this seems to break encapsulation since they can be called directly via the package name. Is there any way of making these methods truly private? As an example,

use SOD::MyOOInterface;

my $instance1 = SOD::MyOOInterface->new();
$instance1->_some_method;  #this produces an error: 
SOD::MyOOInterface::_some_method;   # this results in a 
                                    # successful method call 

Obviously I don't want the direct call of _some_method to succeed. Is there any way of guaranteeing this?

回答1:

Don't use the PBP for object practices. It is very old. In fact, now the best practices regarding Perl and objects can be found in Moose, an almost must-have for Perl.

In short, the way Perl blurs namespaces and classes most methods can be called statically on the class. This is not a bad thing, just don't document it. There is really no reason to want to seal the methods into the instance. Not having private methods is kind of annoying but the convention of not relying on undocumented methods is so strong it has sufficed for our community.

A trait is effectively a role (doesn't permit instantiation) that can be compiled into an object at runtime. This will further obscure the origin of the methods from your typical user (because they won't be in the original class), but it comes at a runtime cost. See MooseX::Traits for more information on traits.

The prepending underscore is a great convention to further state the method is private to peering eyes.

As a last note if you really want to push this issue, you might be able to create an anonymous class with those methods using Class::MOP::Class->create_anon_class()



回答2:

package Foo;

## declare inside-out hashes here:

my %attr_a;
my %attr_b;

## declare private methods here

my $private_1 = sub {
  my $self = shift;
  # can use $attr_a{$self} here...
  ...
};

my $private_2 = sub {
  my $self = shift;
  ... 
};

## public methods here

sub new { ... }

sub public_1 {
  my $self = shift;
  # can access attributes here
  # can call private methods too, with slightly odd syntax:
  my $result = $self->$private_1(@args);
  ...
}

1;


回答3:

Sort of. You can't hide a subroutine that's installed into the symbol table, but you can use a lexical variable to hold a reference to an anonymous subroutine:

package SOD::MyOOInterface;

my $some_method = sub { ... }

$some_method->();

Because $some_method is only visible in the file implementing the class, the subroutine can't be called externally. The drawback is that it can't be called as a method, it must be called as a function. If you want to use it as a method you'll have to pass the object reference explicitly:

$some_method->($obj, @args);


回答4:

The way I deal with this is to add something like this at the start of the method:

my $self = shift;
croak "Instance method called on class" unless ref $self;

It's in no way true encapsulation, but it does mean someone calling you via the package will have to pass an object instance as the first argument. In general with Perl, I find there isn't much point in protecting against malicious users of my API - this just helps me catch situations where I accidentally try calling the method as a class method (which happens more often than I'd like to admit).

Personally, I think the underscore convention + clearly documenting the method as private (or not documenting it at all so it doesn't show up in the POD) is sufficient for real-world use. This is also how it works in Python. It's part of the language philosophy of not restricting users.

A Perl module would prefer that you stay out of its living room because you weren't invited, not because it has a shotgun...