Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

The Power of Objects

by Mutant (Priest)
on Jul 28, 2005 at 11:45 UTC ( #478922=perlmeditation: print w/ replies, xml ) Need Help??

I've never been a huge advocate of OO; I use objects when it's convenient. However, the more code I write, the more I begin to realise that I should be using objects a lot more. A recent solution to a problem I had re-enforced this for me. Although this is probably pretty obvious to a lot of Monks, I thought I'd share my story with you, in case someone finds it useful.

The Problem

I've got a mod_perl application that uses HTML::Template throughout. At the time I wrote it, there wasn't any off-the-shelf framework that I really liked (I'd probably use Catalyst now), and since it was a fairly simple app, I rolled my own.

This just consisted of a Handler that created my own Request object, and passed this to every 'crontroller' function. It included an Apache::Session object, database connection, etc.

The problem was, I needed to add an item to the main menu (displayed on every page) for administrator users only. Previously, there hadn't even been an administrator user, but I did have a 'top.html' template, included by every other template with the menu.

I wrote some code to read the administrator flag from the DB, and store it in the session - nothing tricky there.

Wrapping HTML::Template

I didn't like the thought of passing an adminstrator flag every time I made a call to HTML::Template's param(), so I obviously needed to wrap it.

Luckily, HTML::Template is OO, so all I needed to do was sub-class it. This was pretty straight forward:
package MyApp::HTML::Template; use base qw(HTML::Template); sub new { my $class = shift; my %params = @_; return $class->SUPER::new( %params, die_on_bad_params => 0, ); }
At the same time, I figured I'd default that pesky die_on_bad_params to off, since I hardly ever use it.

Then all I had to do was change every call to HTML::Template->new() to MyApp::HTML::Template->new() - a simple find & replace.

Passing the Flag

Ok, so I had a wrapper for every call to HTML::Template, but now I needed the flag to be passed in to each template. Again, I didn't want to do this every time I called param(), so I obviously needed to override it.

sub param { my $self = shift; my %params = @_; $self->SUPER::param(%params); }

Great, but now I had a problem. The Request object wasn't accessable from my wrapper class - it's out of scope of course. And I didn't want to pass *that* in every time.

After a bit of thinking, I realised I just needed to make my Request class a singleton. This was as simple as inheriting from Apache::Singleton (this module was necessary rather than Class::Singleton, so that the singleton is destroyed at the end of the mod_perl request).

Then, from the param method, I can simply call MyApp::Request->instance() and I'll have the request object with the flag I need, which can then be passed to param().

Conclusion

I've often been suspicious of the 'use objects unless you have a really good reason not to' attitude, but as I learn more, I find that even if there doesn't seem to be a really good reason to use OO at the time you write something, when it comes to maintenance, OO can often make your life a lot easier.

Update: Fixed a spelling error
Update 2: Updated constructor, as per jeffa's suggestion.

Comment on The Power of Objects
Select or Download Code
Re: The Power of Objects
by cees (Curate) on Jul 28, 2005 at 14:33 UTC

    If you would have used an OO framework like CGI::Application for the structure of your app, you would have been able to do this very easily, without needing to subclass HTML::Template.

    package My::App; use base qw(CGI::Application); sub load_tmpl { my $self = shift; my $template = $self->SUPER::load_tmpl(@_); # add some global params here $template->param( ... ); return $template; } sub runmode { my $self = shift; my $template = $self->load_tmpl('filename.tmpl'); $template->param( ... ); return $template->output; }

    OO doesn't work for everything, but if you find yourself passing the same bits of data to all of your functions, then you probably should at least investigate using an object instead.

Re: The Power of Objects
by deprecated (Priest) on Jul 28, 2005 at 16:21 UTC
    It sounds like you have a reasonably good grasp of how objects can be used, but I think you are missing some of the finer points of using objects.

    I recently used objects to much success in a math API. The use of objects allowed me to create "super data types" which both needed to be opaque to the user and also required complex operations which weren't usually available from the standard datatype.

    I'll illustrate a couple cases.

    First, let's describe the project as abstracting lots of math operations on lists of time-aligned numbers. Sure, this could be done with an array of hashes like:

    my $series = [ { $timestamp => $value } # ... ];
    But you still need to be able to provide operations on those values. The example being when I want to pull "all the values" or "all the timestamps" out of the object. You could use map here pretty easily, but you'd have to replicate that code every time.

    When the code gets more complex, like "how do I add all the values from stamps X, Y, ... from two sets of values?", you quickly start needing to come up with componentized code.

    How about the actual data in the objects? Another benefit of having objects is you ensure everyone is using the API the same way. You provide "gettrs" and "settrs" for your API, so hopefully your users are not manually pulling data out of the objects (see Ovid's "thou shalt not covet thy objects internals"), and if you change the methods behind the magic, the users won't notice.

    Additionally, by abstracting away the guts of the object, you get niceties like immutable values:

    my $obj_template = { values => [ sub { 0 }, sub { 3.14 }, sub { 5 }, ], # ... }
    In this case, even if the user dumper()'s our values, they can't see what they are, and you force them to actually use your methods (go on, give it a try).

    Furthermore, by using objects, you can change the way math actually works. Lets say you want to have a switch that adds .0000092 on particular platforms because of a bug you know in that platform. When you have methods that do the work for you, you can have intelligence in the methods that the user doesn't have to know about, or might not understand. For them, they still have $obj->series_add( $another_obj ). They don't know what's going on, and that's generally what people who write robust API's hope for.

    The last thing that I want to hit on is the fact that you can use a module like Params::Validate to incorporate testing into each and every operation your code uses. Cases you never expected to occur can and will occur in the code that uses your code, and your tests will spit out what went wrong, and give you a very good idea of where the bug is. (this of course doesn't take into account that having P::V in your objects will allow you to autogenerate documentation from the validate() and validate_with() calls, and make your code oh so much nicer to work with)

    OOP is usually a little more of a headache (I'd estimate it takes me another 15-20% more coding time up front), but the payoff is huge in the end. Users aren't mucking about with your datatypes, fixes to code are visible in all code which uses your API, and it lets you really set a "substrate" for which others will develop their applications upon. I personally always take that performance hit up front just so I have a better environment to work with down the road.

    zomg, deprecated

    --
    Tilly is my hero.

Re: The Power of Objects
by jeffa (Chancellor) on Jul 28, 2005 at 18:34 UTC

    Just a comment on your constructor -- it doesn't seem right to me. You subclass HTML::Template, but you also instantiate one, rebless it and return that instead of calling SUPER::new(). Seems a little a strange, almost like a Factory ... anyways, here's how i might have coded it instead:

    sub new { my $class = shift; my %params = @_; return $class->SUPER::new( %params, die_on_bad_params => 0, ); }

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      You're right. I'm not sure how I ended up with that. I'll update with your suggestion.
Re: The Power of Objects
by Your Mother (Canon) on Jul 28, 2005 at 23:24 UTC

    A sidebar is the power of templates. Or in this case, Template. It doesn't mind whether it's given an object or a plain old hash ref.

    [% object.accessor % ] works as well as [% hashref.key %] or [% object_with_objects.object.attr %] and [% hash_o_hashes.key.key %]
Re: The Power of Objects
by Mutant (Priest) on Jul 29, 2005 at 16:14 UTC
    Actually, I've discovered my solution above is *wrong*...

    The problem is it's entirely reasonable to call a template without calling param(), eg.
    my $tmpl = HTML::Template->new( filename => 'template.html' ); $tmpl->output();
    Furthermore, param() is called a lot of times internally, behaving differently based on parameter types passed, so it's not trivial to override it.

    In the end, I settled on overriding output(), and calling param() from there, setting the variables I needed. I don't think this changes the main point of my post, but I suppose I should use an example that actually works :)

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://478922]
Approved by Corion
Front-paged by planetscape
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (8)
As of 2014-09-01 13:09 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite cookbook is:










    Results (10 votes), past polls