seattlejohn has asked for the wisdom of the Perl Monks concerning the following question:

I've created a class that allows individual objects to have their own, unique, methods. I've got a good reason (I think) for doing this, but I still can't help but wonder if it's really the nifty trick I think it is, or just a Really Bad Idea. Here's the rationale that led me to this approach -- comments appreciated:

I was thinking that it might simplify some of my larger apps if I had a way to centralize some of the message strings in one place, the idea being that it's perhaps easier to manage the text in a single location rather than have to look at literals scattered across code in a bunch of different modules.

My first thought was to do this using plain old hashes, along these lines:
open MYFILE, $myfile or die $error_messages{file_open_failed};

But this has a couple of big shortcomings, one of them being that you can't easily do any interpolation on the spot. I decided that what I really wanted was to generate error messages on demand using anonymous subroutines, so I built a simple class that would let me create an object along these lines:

my $error_messages = new MessageLibrary({ file_open_failed => sub {"couldn't open file $_[0]: $!"} }); ... open MYFILE, $myfile or die $error_messages->generate_message('file_op +en_failed', $myfile);

This does the job, but I found the calling syntax a bit inelegant. I decided that I would really like to be able to write things this way:
open MYFILE, $myfile or die $error_messages->file_open_failed($myfile);

That just seems more intuitively readable to me. This turned out to be pretty easy to implement by using an AUTOLOAD method to grab the method name and use it as a key to look up the right subroutine in the hash that was passed to the constructor.

But here's where I come up against the philosophical question. I can easily imagine having a few different MessageLibrary objects in my app: one for error messages, one for debug messages, one for progress/status messages. Yet each of them would have different "methods", even though they're all objects of the same class. So my question to all interested monks is, does this seem like a ridiculous perversion of object-oriented principles? I admit that I'm basically using the arrow-notation-method-call syntax as a convenient form of syntactic sugar (I'm a big believer in writing *readable* code)... so I thought it would be wise to get some outside opinions from informed Monks before I forged ahead with this approach.

BTW, the obvious performance impact of this implementation is not a concern.

Replies are listed 'Best First'.
Re: is this a perversion of OO principles?
by khkramer (Scribe) on Jan 07, 2002 at 03:22 UTC

    The way in which you're using the -> operator and AUTOLOAD() is perfectly valid. Your solution is a very clean and perl-ish way to build the API that you need.

    The really nifty thing about the Perl OO framework is how open and flexible it is. As with most everything else Perl, you have a great deal of choice in how you select/combine the OO features of the language for your particular application/architecture.

    As you say, the -> operator is syntactic sugar. But that's not because of how you're using it -- it's *always* syntactice sugar. Really, really useful syntactic sugar. You never have to use the -> operator: you can "drop down a level" and think about Perl objects more along the lines of the way you might think about C structs:

    # common, clean, readable, OO-ish way $foo->bar ( 'bash' ); # less common, probably less readable, way Foo::bar ( $foo, 'bash' );

    You can even intermix the two calling styles, if you really want to.

    As for leaning on AUTOLOAD(), check out how Socket.pm uses it:

    # from 5.6.1's Socket.pm on Linux 2.4 sub AUTOLOAD { my($constname); ($constname = $AUTOLOAD) =~ s/.*:://; my $val = constant($constname, @_ ? $_[0] : 0); if ($! != 0) { my ($pack,$file,$line) = caller; croak "Your vendor has not defined Socket macro $constname, us +ed"; } eval "sub $AUTOLOAD () { $val }"; goto &$AUTOLOAD; }

    Heh. Most of the XS-dependent core modules do really hairy things with AUTOLOAD(). In comparison, your stuff is a paragon of the virtuous, straight and narrow.

    If you want to make the separation between your various MessageLibrary objects clear, you have a few choices. You could add a settable "type" field that would differentiate them. But a more OO-ish way -- if your collections of error messages are pretty stable -- would be to code up the different Library types as classes themselves (MessageLibrary::Error, MessageLibrary::Debug, etc), all inheriting from the base MessageLibrary class. With a properly-written constructor, probably the only thing that would need to be overridden in each child would be the hash of messages. Here's how I tend to code such things (each package in a separate file, and with a framework for calling a class-specific _init() sub, which is often necessary).

    package MessageLibrary; use ... my $hash = { ... }; sub new { my ( $class, %args ) = @_; my $self = {}; bless ( $self, $class ); $self->_init(); return $self; } sub _init { ... } sub AUTLOAD() { ... } 1; #----------------------------- package MessageLibrary::Error; my $hash = { ... something different ... } sub _init { my ( $self, %args ) = @_; ... class-specific init code, if needed ... $self->SUPER::_init ( %args ); } 1;

    Damian Conway's book Object Oriented Perl (isbn ISBN 1884777791 )is a really terrific tutorial and reference on all things Perl-OO.

      Thanks for an extremely informative response. I feel much better knowing I'm not the only one using AUTOLOAD like this ;-)

      I agree with you about Damian's book: I just bought it the other day and I'm feeling enlightened already.

(jeffa) Re: is this a perversion of OO principles?
by jeffa (Bishop) on Jan 07, 2002 at 02:37 UTC
    Seems like a good idea to me, for what it is worth..

    Using an inheritance trick, polymorphism, you could subclass each of the specific MessageLibrary objects so that instead of having different methods in one class, you have many classes with a common overloaded method:

    my $file_error = Error::FileOpen->new(); open MYFILE, $myfile or die $file_error->handle($myfile);
    But, as far as i can tell you with my current knowledge of such problems - all you are doing is moving code from one position to another, you aren't eliminating code.

    I think i'd just rather use:

    open MYFILE, $myfile or die $error_messages->generate_message( 'file_open_failed', $myfile );
    because you don't need to worry about adding code to tailor an error - unless you really need that, don't worry about it. ;)

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    F--F--F--F--F--F--F--F--
    (the triplet paradiddle)
    
      Thanks for the feedback... and just to clarify, when it comes to tailoring errors I am not actually adding code within the MessageLibrary class itself, but specifying it in the object constructors, something like this:

      my $error_messages = MessageLibrary->new({ file_open_error => sub{"Couldn't open $_[0]: $!"}, bad_token => sub{"Invalid token $_[0] encountered on line $_[1]"}, bad_file_format => "Invalid file format", }); my $status_messages = MessageLibrary->new({ starting_parse_phase => "Starting parser...", generating_results => sub {"Generating results for element $_[0]"}, });
      So basically the only stuff in the class package is just a constructor, an AUTOLOAD method, and a couple of little utility methods.

      Update: While rereading this response, I realized there was an aspect of jeffa's point about not having to tailor error messages that I only partially answered. Although you can define error messages in the object constructor, the object also provides default handling for any method you might call. So you could do this: print $status_messages->undefined_msg($a_param, $another); and get a reasonable result. (You can also override this default behavior in the object constructor.)

Re: is this a perversion of OO principles?
by merlyn (Sage) on Jan 07, 2002 at 08:01 UTC
      Class::Prototyped looks like great stuff -- thank you. Now to go digest the meaning of it all ;-)
Re: is this a perversion of OO principles?
by jonjacobmoon (Pilgrim) on Jan 07, 2002 at 03:57 UTC
    Kudos, what an excellent solution to that problem. I commend you.

    In fact, sounds like code that we all might find useful.

    One idea: would you want to make it so that you had four classes, one abstract MessageLibrary class, and three others for each type. You might find in the future that the different types of errors needs different methods of some type. It would also be easier to read because you would know what type you are dealing with. Just an idea that I hope helps.


    I admit it, I am Paco.
      Thanks for the compliment, and I've just posted the code and POD on my site for all to see (gulp). Here's the link:

      http://www.clyman.com/s/pm/messagelibrary/

      This is my first time releasing code into the wild (aside from stuff I've given to clients unlikely to ever look under the covers!)... comments, suggestions, etc. would be much appreciated.

Re: is this a perversion of OO principles?
by tamills (Acolyte) on Jan 07, 2002 at 21:35 UTC
    Class-ifying objects is fundamental notion in the O-O universe. But so is encapsulation. If your code is not going to be using multiples of these message objects often throughout (I wouldn't call 3 or 4 times 'often'), then going to the trouble of rigorously class-ifying your objects may not be a useful return on your investment. Yes, you could come up with an interface definition that helps you to better class-ify your code, but is it worth the trouble, really?

    What you have done that is wholly appropriate is to encapsulate the behavior of your objects using an idiom that you've come up with. It shouldn't be too hard to maintain this at the current level of usage.

    Should you decide that this message object should be mutated and ubiquitous, then you might want to consider developing more O-O type software. Till then, I'd let it slide.

    Drew

    Be careful what you wish for... (Mr. Limpet)

    For where you treasure is there shall your heart be also (Christ)