http://qs1969.pair.com?node_id=157018

I was trying to implement a private class method in a little project I was working on. After thinking about the problem, I came up with the following solution:

package MyClass; use Exporter; use strict; use vars qw/ @EXPORT @EXPORT_FAIL /; @EXPORT = qw/ public_method /; @EXPORT_FAIL = qw/ private_method /; sub new { # bless reference ... } sub public_method { # ... do public stuff including calling private_method } sub private_method { # ... do stuff only class things should know }
Will the Exporter prevent someone from calling my private_method but still allow them to call the public_method?

Replies are listed 'Best First'.
Re: Private Class Methods
by dragonchild (Archbishop) on Apr 05, 2002 at 18:50 UTC
    Method calls bypass Exporter, so no, this won't work.

    There are several ways to do "private" methods.

    1. The standard way is to not bother enforcing private-ness. Just pre-pend an underscore (_) to your method names and let the user consciously make the choice to violate encapsulation.
    2. You can put a
      sub _my_private_method { my $self = shift; die "Private method, jerk!" unless UNIVERSAL::isa($self, __PACKAGE__); # ... Continue on here as normal }
      That's known as a gatekeeper. I haven't seen that a lot. That kind of private allows inheritance to work. The other way, requiring that you must be of that class and that class alone would be to do something like ref $self eq __PACKAGE__, more akin to C++'s private.
    3. This applies more to data, but you can change your reference from a hashref to a coderef, effectively making all your object instances closures. This would make all your data private. You could extend it to methods, I suppose. I wouldn't reccomend it because it's extremely non-standard and would be very hard to maintain. There's also a minor speed hit, but it's not that major.
    All in all, the best method I've found is #1. Just trust your users. It's the easiest policy to implement.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      ++ on #1. Don't waste your time on things like this. Name it _foo() and be done with it.
      ++ on #1.

      But #2 will fail on "indirect" private calls, i.e. where class A implements

      sub public_method { # do something $self->_my_private_method(); # do something else }
      and then public_method is invoked on a derived object. Bad bad.
      --
      Mike
(tye)Re: Private Class Methods
by tye (Sage) on Apr 05, 2002 at 22:32 UTC

    I've starting writing more OO code more like:

    package My::Class; use vars qw( $VERSION ); BEGIN { $VERSION= 1.001 } package My::Class::_internal; sub My::Class::new { my $self= bless {}, "My::Class::_object"; #... return $self; } sub utilityFunction { my $self= shift @_; # ... } sub My::Class::_object::method { my $self= shift @_; # ... utilityFunction( $self, @args ); # ... }
    That is, have only class methods in the My::Class namespace, have only object methods in the My::Class::_object namespace, and put utility function in some other namespace (package). Note that all of the functions are actually compiled in the "internal" package (even though they are named into a different package) so that they can call utility function w/o specifying a package name.

    This prevents the module user from making the following mistakes:

    # Calling a class method via an object: my $obj2= $obj1->new(); # Calling an object method via the class name: my $ret= My::Class->method() # Using my utility function: $obj->utilityFunction();
    Note that this prevents naive inheritance from working. I don't mind much as I think that inheritance should be mostly a last-resort technique and should not be used with a class that wasn't specifically designed to allow inheritance. You can also think of My::Class as a "factory" class, if that makes you feel better (in fact, several of my recent classes really were factory classes).

    Of course, you might well want to follow OO Perl tradition (what little there is of it, OO Perl being so young) and allow $obj->new(). One way to do that is by "exporting" sub new:     *My::Class::_object::new= \&My::Class::new; or you can just not separate your object methods from your class methods, instead just separating your utility functions (note that the utility functions are not methods because calling them as methods just adds overhead in order to search the inheritance heirarchy but you don't want to allow them to be overridden via inheritance so that would just be a waste).

    The following example does that and even uses Exporter.pm. You can also use Exporter.pm when you separate everything (like the first example does), and it can be quite nice, especially if you have lots of methods. It is even nicer if you have multiple object types and some methods are shared among multiple object types (yes, you can share methods with less flexibility via inheritance).

    package My::Class; use vars qw( $VERSION ); BEGIN { $VERSION= 1.001 } # Following line moved down to increase robustness: ## My::Class::_internal->import( ":DEFAULT" ); package My::Class::_internal; require Exporter; use vars qw( @EXPORT_OK ); BEGIN { *import= \&Exporter::import; @EXPORT_OK= qw( new method ); } sub utilityFunction { my $self= shift @_; # ... } sub new { my $class= shift @_; my $proto; $class= ref( $proto= $class ) if ref $class; my $self= bless {}, $class; #... return $self; } sub method { my $self= shift @_; # ... utilityFunction( $self, @args ); # ... } package My::Class; My::Class::_internal->import( ":DEFAULT" );
    Note that there are lots of ways you can deal with getting things to happen in the proper order here. The above commented line would work because it is not in a BEGIN block while "everything" else is. But I show the last two lines instead as they are less sensitive to having something in a BEGIN block that shouldn't be or vice versa.

    You can even create a separate My/Class/_internal.pm file so that a standard

    package My::Class; use My::Class::_internal;
    would work.

            - tye (but my friends call me "Tye")
Re: Private Class Methods
by japhy (Canon) on Apr 05, 2002 at 19:12 UTC
    By "private" do you mean it can be only called from inside your actual module? If so, then you could do something like this. You can get around it, but it requires a bit of effort (and the know-how). Here's a function to turn any method into a private method.
    sub privacy { my $owner = __PACKAGE__; my ($pkg, $file, $func) = (caller 1)[0,1,3]; unless ($pkg eq $owner and $file eq __FILE__) { require Carp; Carp::croak("'$func' is a private method of $owner"); } }

    _____________________________________________________
    Jeff[japhy]Pinyan: Perl, regex, and perl hacker, who'd like a (from-home) job
    s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

Re: Private Class Methods
by premchai21 (Curate) on Apr 05, 2002 at 18:46 UTC

    No -- Exporter only controls which symbols get exported (i.e. copied to, sort of) the calling package. Object method lookups get done by the bless system, which goes directly to the package symbol table, and which Exporter does not affect.

    One way to achieve what you're looking for might be to have private_method use caller to determine whether it is being called from within the package in question or not...

Re: Private Class Methods
by Kanji (Parson) on Apr 05, 2002 at 19:22 UTC

    Yet another way to do it is with the use of appropriately scoped anonymous subs...

    my $private_method = sub { # ... do stuff only class things should know }; $private_method->();

        --k.


Re: Private Class Methods
by ehdonhon (Curate) on Apr 05, 2002 at 18:52 UTC
Re: Private Class Methods
by pdcawley (Hermit) on Apr 06, 2002 at 18:55 UTC
    You could always wait for Perl 6.
    class Foo { my method bar {...} }
    et viola! only Foos can call bar. Sorry if that's not exactly helpful. The other option is:
    sub bar { croak "Oi! You're not a foo!" unless $_[0]->isa(__PACKAGE__); }