Of late, I've been pondering the issue of private and protected methods in Perl. I actually like that Perl doesn't enforce them. In practice, the convention of preceeding "private" method names with underscores works quite well (as long as you include a note in the documentation explaining the convention). The pre-underscore convention fits the Perl spirit quite nicely: people who really want to mess with the class internally can do so, but they're on their own.

That said, though, there are times when slightly more robust enforcement of private and protected methods makes sense. Of course, there are relatively simply hacks to circumvent every approach I've seen, and that's OK: the idea is stronger discouragement, not complete protection.

There are no end of solutions to make method subs private, the most common one I've seen being some variation on:

sub _private_sub { die "_private_sub is private!" unless caller eq __PACKAGE__; # private stuff # }

The result is that only the package where _private_sub is defined can invoke it as a method. I've used this in the past where I found it sensible, and it has always worked well enough.

Unfortunately, such an approach does not address protected methods. Now, I've seen the term used a few ways, so let me define it: a protected method is a class method that may only be called by the package which defines it and by any class which extends the defining package. In other words, if I have ModuleA define _protected_sub_a, and ModuleB use base 'ModuleA', then both Modules can legally invoke _protected_sub_a directly. Were it a private sub, only ModuleA could invoke it.

With my current client, protected methods in Perl as defined above have become a requirement for me for the first time. What I came up with was a simple approach -- a base class that includes the following method:

sub PROTECTED { my $self = shift; my @caller = caller(1); $caller[3] =~ m{(.*)::(.*)$}; my ($pack, $sub) = ($1,$2); local $Carp::CarpLevel += 1; confess "method '$sub' is protected by '$pack'" unless ($caller[0]->can($sub)); return $self; }

If extending this base class (we'll call it BaseClass), we can do the following:

use base 'BaseClass'; # some stuff # sub _protected_sub { my $self = shift->PROTECTED; print "This is a protected sub!"; }

The end result is only this class and any which extend it can directly invoke _protected_sub. Of course, I know this isn't perfect protection and a consumer program could, with some work, cheat and call the protected methods. I'm OK with that, all I'm looking at is strong discouragement. In this case, attempt to inappropriately call a protected method (in this case, test.pl tried to call a method from a LocalTest2 object which extends the LocalTest class which in turn defines the test2 method) dies with something like:

method 'test2' is protected by 'LocalTest' at LocalTest.pm line 12 LocalTest::test2('LocalTest2=HASH(0x18633a4)') called at test. +pl line 15 main::test() called at test.pl line 20

While this is a Meditation, I'm also interested in seeking some wisdom. What are the potential problems of this approach? What existing solutions did I miss, and how are they better/worse?

Many, many monks here are wiser than I, and I'd appreciate any critique.

<radiant.matrix>
A collection of thoughts and links from the minds of geeks
The Code that can be seen is not the true Code
I haven't found a problem yet that can't be solved by a well-placed trebuchet

Replies are listed 'Best First'.
Re: Private and Protected class methods
by stvn (Monsignor) on Sep 06, 2006 at 21:48 UTC
    There are no end of solutions to make method subs private, the most common one I've seen being some variation on:
    sub _private_sub { die "_private_sub is private!" unless caller eq __PACKAGE__; # private stuff # }
    The result is that only the package where _private_sub is defined can invoke it as a method. I've used this in the past where I found it sensible, and it has always worked well enough.

    While this is a common way to implement private method, it is completely wrong.

    If the perl method dispatcher encounters a private method, it should not die, but instead it should skip it an continue the dispatch. Take this pseudo-perl example:

    package Foo; sub bar : public { ... } package Bar; use base 'Foo'; sub bar : private { ... } package Baz; use base 'Bar';
    Now if under your scheme, this code:
    Baz->new->bar;
    would die, but that is not correct, it should succeed and call Foo::bar.

    Method dispatching which includes Private/Protected/Public can have many subtle edge cases, some of which are not easily (if at all) solveable with the basic mechanisms provided in perl.

    -stvn

      That's an excellent point! Thank you!

      It seems like to do protection "properly", one would need some external knowledge of whether subs were supposed to be protected or not -- and that seems like a lot of work. I will be bringing this up with the design team for sure.

      ++!

      <radiant.matrix>
      A collection of thoughts and links from the minds of geeks
      The Code that can be seen is not the true Code
      I haven't found a problem yet that can't be solved by a well-placed trebuchet
Re: Private and Protected class methods
by hardburn (Abbot) on Sep 06, 2006 at 18:50 UTC

    I usually use the underscore convention to indicate protected methods. For private methods, subrefs held in lexical vars are my prefered way:

    my $private_method = sub { my ($self) = @_; . . . }; $self->$private_method();

    If you know how to lexical scope like a fox, you can even keep it private from different parts of your class:

    { my $very_private_method = sub { my ($self) = @_; . . . }; $self->$very_private_method(); # Can call it here } # But this is a compile error under "use strict 'vars'" $self->$very_private_method();

    That's a level of privacy most languages don't have.

    "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

Re: Private and Protected class methods
by chromatic (Archbishop) on Sep 07, 2006 at 05:13 UTC

    I suspect (but can't prove at this hour) that your can() call suffers from the false cognate problem.

      false cognate problem for those like me that haven't encountered the phrase,


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
      It does. Suppose that package A has a method called foo() and package B has a protected method called foo(). Suppose further that $b is an object of class B. Then in package A if we call $b->foo(), the call will succeed because in B we check to see if A->can("foo") and find that it can. Even though the two foos have nothing to do with each other.
Re: Private and Protected class methods
by samtregar (Abbot) on Sep 06, 2006 at 18:08 UTC
    That said, though, there are times when slightly more robust enforcement of private and protected methods makes sense.

    There are? I've yet to find myself in such a situation. This looks like a complete waste of time (human and CPU) to me! I much prefer to rely on coding standards and code reviews to avoid the kind of problems this is presumably attempting to address.

    -sam

      For me, the important distinction is accidental vs. intentional. Steps to discourage intentional misuse are mostly futile and are more than a waste, IMO. I usually find myself intentionally violating such restrictions because the author of the module lacked good design skills and making it a pain for me to overcome your stupid mistakes isn't a worth-while feature. And I don't want to make it a pain for other people to overcome my stupid mistakes.

      Trapping accidenal misuse can often be a good thing. Code reviews are a Good Thing™, but I don't want to rely on code reviews for enforcing complex restrictions when those can be trapped more reliably in an automated fashion.

      But I seriously doubt I'd make use of such a complex (my first impression) solution as proposed above. I'd need to see what real or likely accidental misuses this is intended to trap. My first impression of the above solution is that is appears broken anyway [it seems like it should be seeing if (caller(2)'s package)->can(caller(1)'s method name)]. So I probably don't understand the point of it.

      And I doubt I'd use inheritance to get this helper subroutine available to every class that wants to define protected subs. I'd probably just export it instead (into the "implementation" package that method code is compiled under so that I don't polute the object method namespace with things that aren't meant to be called as methods by users of my objects).

      As my impression of the idea of this sinks in, I start to think that I might use something similar to this in some cases. Removing inheritance and the polution of the namespace of object methods from its implementation makes me more comfortable with it. I'm not sure that the way @ISA is searched using can() is the best approach here. Then there is the complication that my methods are usually compiled into a package other than the one that objects get blessed into and I wouldn't want this check to incorrectly fire because of that. I'd have to do some digging on which packages are returned in which values from caller and some thinking about what really is supposed to be enforced here. No, I suspect that my OO "best practices" would break this code.

      I also suspect that stepping back and looking at the specific problem may lead to a solution other than "protected methods".

      - tye        

        My first impression of the above solution is that is appears broken anyway {it seems like it should be seeing if (caller(2)'s package)->can(caller(1)'s method name)}. So I probably don't understand the point of it.

        Actually, no (I have tested this). The subroutine name from caller is in the form Package::sub_name, where Package is always the name of the definining package. So, I check to see "who invoked the sub that called me?" and see if that package can sub_name. It works. It took me a while to get right, but it works.

        And I doubt I'd use inheritance to get this helper subroutine available to every class that wants to define protected subs.

        That's not exactly what I'm doing. This particular client has asked me to write an organization-wide base class (as in "all our classes will inherit from this"). This feature is being included there, so it must propogate via inheritance. That's not a design decision, it's a requirement.

        I'm not sure that the way @ISA is searched using can() is the best approach here.

        See, it's the only way I know of. I'd love to hear if there was another, better way. Care to elaborate?

        No, I suspect that my OO "best practices" would break this code.

        Can you be more specific? What do you do that would break this? How would it break?

        I also suspect that stepping back and looking at the specific problem may lead to a solution other than "protected methods".

        Like I said in my intro -- my personal feeling is to simply establish a coding standard that has a convention for naming protected and private methods, and let the code reviews, etc. catch things. That said, I also understand my client's point of view -- sometimes it's nice to kvetch about things like this (like the Perl::BestPractices module).

        The central point being that the ability to define private and protected methods is a requirement of my client, regardless of how I feel about them, so I need to find a workable solution that won't cause too many issues. You seem to suggest that the solution I'm proposing would cause some issues, but I would appreciate expansion of what those might be and what I have to consider to devise a work-around.

        <radiant.matrix>
        A collection of thoughts and links from the minds of geeks
        The Code that can be seen is not the true Code
        I haven't found a problem yet that can't be solved by a well-placed trebuchet
        I usually find myself intentionally violating such restrictions because the author of the module lacked good design skills and making it a pain for me to overcome your stupid mistakes isn't a worth-while feature.

        I don't see badly written modules as an excuse to violate encapsulation: I see it as a reason to fix the module in question.

        If you're really that much smarter than the module designer, and know so much about the internals of his design that you can reliably call functions that manage the system's internal state without any fear of ill side effects, why not just change the module to be what you want it to be?

Re: Private and Protected class methods
by tilly (Archbishop) on Sep 06, 2006 at 19:50 UTC
    A more general approach to providing protected methods may be implemented using Class::AutoloadCAN. Which I never really completed. (But to be fair, I don't know if anyone is actually interested in using it.) See RFC: Class::AutoloadCAN for my ideas on how to complete that implementation.

    That module was meant to be my answer to the problems I raised in Why breaking can() is acceptable, and hence was more proof of concept than anything else. But it can be made to work.

Re: Private and Protected class methods
by rir (Vicar) on Sep 06, 2006 at 20:53 UTC
    I haven't had much call for protected methods. What is the problem with this?
    package Parent; sub protected_sub { my $me = shift; die unless $me->isa(__PACKAGE__); print $me, " -> Parent::protected_sub$/"; }
    Be well,
    rir
      What is the problem with this?

      Calling isa() and forcing inheritance sucks.

        Why? I mean, I can see the argument for not using protected subs at all, but given that it's a requirement, and that the point of "protected" is that the class and its children (thus, inheritence is implied) can call it, what's the issue with that approach?

        <radiant.matrix>
        A collection of thoughts and links from the minds of geeks
        The Code that can be seen is not the true Code
        I haven't found a problem yet that can't be solved by a well-placed trebuchet