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

Hello all, I'm just mulling over a framework design for this application I've been asked to build. Eventually, I wish to run this under mod_perl, so I'm plumping for the completely modular approach (with a start-up script loading modules). Here's my question however. I need to be able to process a number of actions submitted by the user, say "open_file" and "display_line". I'd like to keep those subroutines in a specific module which is AUTOLOADed (say base::Actions::DisplayStuff), just to prevent me from implicitly defining what every "action" does and importing all the modules. i.e. -
if ($act eq 'open_file'){ stuff; } elsif ( $act eq 'open_stuff') { stuff; } elsif ( $act eq 'speak' ) { stuff; }
Firstly I'm not sure whether this is an correct approach to adopt, and secondly I'm wondering how I might structure the AUTOLOAD function to load these extra modules. Should I actually use "require", or do I need to use that funky goto method ? Obviously I'm looking for maximum flexibility, but also reasonable execution speed.

I hope someone can understand this raving mess of a question ;)

kindest,

>joe

Replies are listed 'Best First'.
Re: OO/Application Framework question
by Ovid (Cardinal) on Apr 17, 2003 at 22:43 UTC

    You are on the right path. When you have a bunch of if/else statements on a single variable, it is sometimes appropriate to ask if polymorphism would help.

    if ($act eq 'open_file'){ #stuff; } elsif ( $act eq 'open_stuff') { # stuff; } elsif ( $act eq 'speak' ) { # stuff; }

    The problem with the above code is that it's more likely to have a bug (what if you forget to add a new action?) and it can be cumbersome to read. How about creating a factory that returns an object that's been blessed into the appropriate class, based upon the value of $act?

    package My::Actions; use Carp; sub factory { my ($class, $action) = @_; my %actions = ( open_file => 'My::Actions::OpenFile', open_stuff => 'My::Actions::OpenStuff', speak => 'My::Actions::Speak' ); croak "Unknown action ($action)" unless exists $actions{$action}; my $class = $actions{$action}; return $class->new; } sub do_stuff { my $class = shift; croak "Class ($class) did not implement do_stuff()"; }

    In the above snippet, all of the classes in the %actions hash inherit from My::Actions and should have a do_stuff() method (see snippet below).

    use My::Actions; my $object = My::Actions->factory($act); $object->do_stuff;

    In the above code, the do_stuff() method will croak if you forgot to create it, otherwise your generated objects can now do what you want without the cumbersome if/else logic. Further, by merely creating a new class when a new action is required (and adding it to the factory hash), your main code does not need to change.

    The only caveat that I have is that I you need to analyze your code to figure out if a factory pattern is warranted and fits your object model. Factories can be fun and solve thorny problems. They can also create a confusing mess when not used correctly.

    ... and in retrospect, I kind of think I wasn't quite answering your question ...

    Cheers,
    Ovid

    New address of my CGI Course.
    Silence is Evil (feel free to copy and distribute widely - note copyright text)

      A factory. Brilliant! My CS classes must just have passed straight through my head, and out again ;)

      Just to probe this question further (if I may), would this work well with mod_perl. I mean, ideally I'd like it to work across platforms with minor changes, but would these actions be loaded each time, or could they be stored in memory. I'm at a bit of a crossroad, I don't know whether to make it mod_perl specific, or allow the system some breathing space...

      So back to the question at hand, would I bless the parent module (containing the Apache::Request,etc data) into the Actions/Factory module, and then just pass this to the Actions sub-packages. That way they'd have access to the Database objects I set up, and the Apache::Request handler. Does that seem about right ?

      /me thinks he needs a mod_perl book

      kindest regards,

      >joe

Re: OO/Application Framework question
by samtregar (Abbot) on Apr 17, 2003 at 22:39 UTC
    Two possibly useful reactions:

    1. Check out CGI::Application. It's an object-oriented framework for creating web applications which works great under mod_perl.
    2. Using AUTOLOAD to load modules on-demand will waste memory under mod_perl. This is because modules loaded after startup will not be shared between Apache children and will be loaded multiple times. You will get much better performance by simply loading everything you need upfront during Apache startup.

    -sam

Re: OO/Application Framework question
by perrin (Chancellor) on Apr 17, 2003 at 22:44 UTC
    You may want to look at Apache::Dispatch, which can automatically load modules as needed. As samtregar pointed out though, you will eventually want to load all the modules you use in your startup.pl to save memory. At that point, Apache::Dispatch would just be helping to map URLs to modules.
Re: OO/Application Framework question
by dragonchild (Archbishop) on Apr 17, 2003 at 23:13 UTC
    Ovid has the right solution, given what you've laid out. There may be other considerations, but you didn't tell us what they were. :-)

    To continue further, doing what Ovid has described requires that you be willing to do this kind of analysis of your requirements. However, if it's an application of any reasonable complexity and/or lifespan, the cost of the time spent doing this will be realized over 100-fold during the first 6 months of development. (And, no, that's not an exagerration. If anything, it's a very conservative estimate.) The very fact you say raving mess of a question means that you feel you don't have a very good understanding of your requirements, let alone how you want to go about designing it. To me, that's a very large blinking red flag that my development effort is going to be marred by cost overruns, late deliveries, and a large ulcer. Personally, I think those are all things to avoid. But, Slavercise has been in the news lately, so go figure!

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

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

      Hiya dragonchild,

      I understand the requirements perfectly, as they been expressed to me within a tight set of rules. Yet I want to make the development easier on myself, and experiment at the same time :) The whole system has already been finished in the typical CGI-type environment, yet as ever, TMTOWTDI, and I'm looking to compress those scripts down into modules, and execute it from just a base script. Just to simply speed the whole operation up.

      best,

      >joe

        As you've said, the system is already up and running, so, obviously, the business owners have approved your translation of their requirements into reality. And, frankly, you can stop there.

        However, I feel that, if an application is to be maintainable, it needs to be as decoupled as possible. To safely decouple something, you need to understand the business logic behind requirements. You need to be able to identify those things that seem to be popping up in the system with alarming regularity. You need to be able to identify those infrastructure features that would be really neat to have, but don't do add a single whit of functionality to the system as it stands. And, beyond all of that, you absolutely have to have the fire in your belly that makes you want to make it as beautiful as it can be.

        If you're missing any of those things, don't read any further, cause what I'm going to say doesn't apply to you.