Hack 49. Make Your Objects Truly Polymorphic


Build classes based on what they do, not how they inherit.

Many tutorials and books declare confidently that inheritance is a central feature of object-oriented programming.

They're wrong.

Polymorphism is much, much more important. It matters that when you call log( ) on an object that knows how to log its internal state it does so, not that it inherits from some abstract Logger class somewhere or that it calculates a natural log. Perl 6 encourages this type of design with roles. In Perl 5, you can either build it yourself or use Class::Trait to decompose complex operations into natural, named groups of methods.

That sounds awfully abstractbut if you have a complex problem you can decompose appropriately, you can write just a little bit of code and accomplish quite a bit.

The Hack

Imagine that you're building an application with properly abstracted model, view, and controller. You have multiple output typesstandard XHTML, cut-down-XHTML for mobile devices, and Ajax or JSON output for RESTful web services and user interface goodness.

Every possible view has a corresponding view class. So far the design makes sense. Yet as your code handles an incoming request and decides what to do with it, how do you decide which view to use? Worse, if you have multiple views, how do you build the appropriate classes without going crazy for all of the combinations?

If you cheat a little bit and declare your views as traits, you can apply them to the model objects and render the data appropriately.

Here's an example model from which the concrete Uncle and Nephew classes both inherit:

package Model; sub new {     my ($class, %args) = @_;     bless \\%args, $class; } sub get_data {     my $self = shift;     my %data = map { $_ => $self->{$_} } qw( name occupation age );     return \\%data; } 1;

The views are pretty simple, too:

package View; use Class::Trait 'base'; package TextView; use base 'View'; sub render {     my $self = shift;     printf( "My name is %s.  I am an %s and I am %d years old.\\n",         @{ $self->get_data( ) }{qw( name occupation age )} ); } package YAMLView; use YAML; use base 'View'; sub render {     my $self = shift;     print Dump $self->get_data( ); } 1;

The text view displays a nicely formatted English string, while the YAML view spits out a serialized version of the data structure. Now all the controller class has to do is to create the appropriate model object and apply the appropriate view to it before calling render( ):

# use model and view classes # create the appropriate model objects my $uncle  = Uncle->new(     name => 'Bob', occupation => 'Uncle', age => 50 ); my $nephew = Nephew->new(     name => 'Jacob', occupation => 'Agent of Chaos', age => 3 ); # apply the appropriate views Class::Trait->apply( $uncle,  'TextView' ); Class::Trait->apply( $nephew, 'YAMLView' ); # display the results $uncle->render( ); $nephew->render( );

Running the Hack

The code produces:

My name is Bob.  I am an Uncle and I am 50 years old. --- age: 3 name: Jacob occupation: Agent of Chaos

Hacking the Hack

If that were all that traits and roles are, that would still be useful. There's more though! Class::Trait also provides a does( ) method which you can use to query the capabilities of an object. If you could possibly receive an object that already has a built-in view (a debugging model, for example), call does to see if it does already do a view:

Class::Trait->apply( $uncle, $view_type ) unless $uncle->does( 'View' );

You also don't have to have your traits inherit from a base trait. If all of the code that uses objects and classes with traits checks does( ) instead of Perl's isa( ) method, you can have traits that do the right thing without having any relationship, code- or inheritance-wise, with any other traits.

This is especially useful for working with proxied, logged, or tested models and views.



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net