views:

421

answers:

4

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?

+6  A: 

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);
Michael Carman
@Micheal Yeah, I was hoping to avoid the "hackish" nature of scalars holding references to anonymous subs!
ennuikiller
If you're using inside-out objects you're already doing rather "hackish" OO. Hiding private methods via CODE refs would seem to fit in with that quite well.
Michael Carman
@Michael actually once you get used to every attribute as a hash feature of inside-out objects it becomes quite natural!
ennuikiller
You can call it as a method. `$self->$some_method()` works fine. Of course you need to be in a public method to have `$self` available.
friedo
+1  A: 

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...

ire_and_curses
+4  A: 

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()

Evan Carroll
I agree. Why make it impossible for someone to call a private method? If it's not documented it's not part of the public interface. Not only does this break encapsulation, but is also clearly not going to be supported by the Author. But why should the author care? If I decide to use my laptop as a hammer, this is clearly not what the makes of my hardware intended and they will not support or repair it. But should they go out of their way to prevent me from doing that or just leave me to my stupidity.
mpeters
It is because some people believe they should be able to establish interface through code, to stop people from hanging themselves, etc. etc. The underscore, undocumented method is convention you have to learn, using it doesn't even trigger a `warning.` Some languages like C# even invent `sealed` classes to push this point.
Evan Carroll
@EvanCarroll thanks, I'll look into using Moose
ennuikiller
Unfortunately in some companies, some developers will ignore the documentation and take advantage of any private interface they can exploit. Debugging "helpers" like Data::Dumper make it all too easy to poke inside an object's innards -- something that Inside-Out objects blocks to some degree.
Ether
That would be the totally wrong reason to use an inside-out, BUT to further make my point check MooseX::InsideOut http://search.cpan.org/~hdp/MooseX-InsideOut-0.104/lib/MooseX/InsideOut.pm
Evan Carroll
It is trivial to treat inside-out objects as if they were hashref-based objects. Scrottie has a CPAN module to do this. So basically, the inside-out technique doesn't obscure your interface anymore than naming methods "_foo" does.
jrockway
jrockway: It's not "trivial". It's just possible. Scott's module uses Data::Alias, Padwalker, B. The interface he provides may make it look trivial, but the contortions he has to go through sure show it's black magic under the hood.
tsee
Dave Sherohman
@tsee: all Perl uses that stuff under the hood. You just don't see it in Perl, as it's nicely hidden away as C that gets deleted from your machine after "make install" ;)
jrockway
+6  A: 
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;
Randal Schwartz