exporter proxy
Post on 27-May-2015
510 Views
Preview:
DESCRIPTION
TRANSCRIPT
Exporter::Proxy
Splitting up modules to help your sanity.
Steven LembarkWorkhorse Computing
Stuck in Lodi Again
● We've all been there: the 4000line module.● It began small enough... then it grew.● Cannedquery modules are classics for this:
● In the beginning was “userid”, and it was good, enough.● Then it was split into get_userid/set_userid.● Then it branched into get_dept_userid,
get_general_userid and set_*.
● That was then, your cleanup is now.
Welcome to the Machine
● You have to clean up the module.● You need to split the thing into usable chunks.
● Separating out namespaces.● Keeping shared variables in a reasonable place.● Keeping utility subs out of the way.● By tomorrow.
In parallel universes, in constant time.
This is Perl, however.
● So you will practice True Lazyness (c). ● This means doing it once.● Doing it right.● And never having to do it again.
Lazyness is a virtue
● Exporting in Perl should be easy, but Exporter is complicated (and error prone).
● Breaking up large modules into manageable chunks often takes too much work.
● Both of these should be easy.
Unlazy Shared Variables.
● Exporter allows sharing variables.● If you inherit from it.● If you keep proper track of EXPORT_OK,
EXPORT, EXPORT_TAGS.● If, hopefully, @foo never changes to %foo.● There has to be a better way...
False Lazyness:
● Break the module up by function, the names don't immediately collide: Lookup::userid, Modify::userid
● But, you can't inherit both classes without collisions.● You could try Lookup::Dept::userid and
Modify::Dept::userid.● But do you really want to see:$qryobj->Lookup::Dept::userid( ... );
Let alone type it?
UnLazy TopHalf Modules
● Another way to split up the modules is a single God Object dispatcher that “figures out” how to dispatch into the various pieces of the original module.
● This ends up being either a unmaintainable ifblock, an unreadable nary ternary op, or the Switch From Hell (tm).
● The problem is that the caller knows what they want: expose them to the decisions and let them make the proper one.
Exporter::Proxy
● This is Perl, there is a lazy way.● E::P simplifies you work by:
● Exporting symbols.● Installing an import sub to export the symbols.● Optionally installing a dispatch sub.
● This allows you to split your modules into layers:● A tophalf dispatcher that combines the moduels.● The bottomhalf modules that actually do the work.● Common shared variables, all stored in one place.
“The simplest interface is none at all”
● Instead of having to define a set of variables for your set of variables, just:use Exporter::Proxy qw( verbose debug foobar );
● Whatever names you provide are exported as symbols via Symbol:
● $verbose, &verbose, @verbose, %verbose, all you have to export is “verbose”.
my $source = qualify_to_ref $_, $source;my $install = qualify_to_ref $_, $caller;*$install = *$source;
Lazy Dispatcher
● These look pretty much the same: decide where the call goes, send it there, and get out of the way.
● This is done in Exporter::Proxy with the “dispatch=” argument.
● The dispatcher is exported by default.● Each one dispatches calls within its own package:
my $name = splice @_, 1, 1;my $dest = $package->can( $name )or croak “$package cannot '$name'”;
goto &$dest;
Splitting Up The Interface
● You've probably witnessed *NIX device drivers .● The top half validates and dispatches the call, the
bottom half just does things.● The top half doesn't care what does on, it just sends
things where they belong.● The bottom half doesn't care why it does things, it
just does them.
Breaking Up A Module
● You can usually break the combined interface up into functional groups:● “modify” vs. “lookup”● “department” vs. “office” vs. “location”.● “Taxid”, “GenBank”, “Medline”, “MeSH”.
● You can also combine any shared variables into a single module that exports them as needed.
Looking At A Query Module
● “modify” and “lookup” are probably good divisions.● The SQL for canned queries is probably sharable. ● The query methods don't care who their caller is.● The dispatcher doesn't care why the methods were
called, it just has to hand back the data.● Code something like:
$query->lookup( department => @argz );
$query->modify( department => @argz );
● Is reasonably mnemonic and easily extended.
Bottom Half Does the Work.
● The bottom half implements methods for specific tasks and exports a single dispatcher for them.
● Break the module up into Query::Lookup, Query::Modify, Query::Shared.
● Lookup & Modify install a dispatcher:use Exporter::Proxy qw( dispatch=lookup );
use Exporter::Proxy qw( dispatch=query );
● They pull in shared content by Using Query::Shared:package Query::Modify;
use Query::Shared qw( modify_dml ); # SQL hash
The Public Interface is Truly Lazy
● Bottom halves export their dispatchers: just collect them together into a single API.package Query;
use Query::Lookup;use Query::Modify;use Query::Shared qw( verbose );
sub verbose { @_ ? $verbose = shift : $verbose }
42__END__
● The Top Half is DUMB: there are no decisions here, no special cases, no edge cases.
What the Caller Sees
● Anyone using “Query” just knows that its API includes “lookup”, “modify”, and “verbose”.
● The methods take a first argument of what to lookup or modify, and whatever arguments it needs.
● They don't have to know about the bottomhalf, shared variables, or utility sub's that aren't exported by the bottomhalf modules into the API.
● This is what makes the Top Half dumb: the caller makes their own decisions on what to call.
Lazy Growth
● Refactoring the interface into subject areas is also straightforward:● Add new modules “department”, “office”, “frobnicate”.● Each has a mnemonic dispatcher, say, “dept”, “office”,
and “frob”.● They implement whatever methods describe the action.● They can share the existing SQL or define their own.● The caller uses, say,
$query->dept( id => $dept_name );
Summary
● True Lazyness is a virtue.● Dispatching interfaces offer a simple way to
segregate the classes.● Tophalf classes can implement mnemonic API's.● Bottom duty can be bearable with Exporter::Proxy.
top related