Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Replacing namespaces

by smferris (Beadle)
on Dec 10, 2001 at 23:36 UTC ( #130753=perlquestion: print w/replies, xml ) Need Help??

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

Example:

----
#!/app/perl5.005/bin/perl package First; sub new { bless {}, 'First' }; sub test { my $self=shift; bless $self, 'Second'; } 1; package Second; sub test { my $self=shift; print "Hello World!\n"; } 1; package main; my $t=new First(); print $t,"\n"; # Output: First=HASH(0x2864) $t->test; # no output; print $t,"\n"; # Output: Second=HASH(0xc2864) $t->test; # Output: Hello World!
----

I have a data(base) load script I am working. It's quite flexible as to the source and the destination of the data. (Could be to<=>from a file, database, etc) Currently I have conditionals to work around problems. EG: Transfering from Sybase 2 Sybase I have a problem with text fields. You can't bind text. Oracle 2 Oracle, longs are implicitly bound (binded?) as a varchar which means any data over 4000 chars and oracle complains, etc. I can work around these issues w/ conditionals but I thought a more elegent approach would be to move this to a module. If the load doesn't work one way, try another approach. (Replace the current namespace w/ a new one and try that.)

This is especially attractive, because the next approach may not even resemble the one that failed. Maybe it fails over to native tools when both backends are the same. Oracle: Exp/Imp.. Sybase: BCP.. Neither of these would require a loop on cursor approach. In fact, it wouldn't require a loop at all! But a select<=>insert cursor loop is "easier" for Sybase to Oracle. (I tried bcp->sql*load. A terrible hack!)

Anyway.. My question is: Is this approach as ellegant as I think? Is it a bad hack and prone to problems? The "original" caller may need to know how the data was actually transfered so replacing the namespace is the only decent way I see to do this. It sounds good to me, being fairly new to PerlOO, and I've always been told, if it looks to good to be true, it probably is.

Your thoughts would be greatly appreciated!

SMF 8)

Replies are listed 'Best First'.
Re: Replacing namespaces
by dragonchild (Archbishop) on Dec 11, 2001 at 00:14 UTC
    This is a very elegant solution to the type of problem you're describing. For those who're confused, what smferris is asking is this:

    I have an action I wish to perform on a bunch of data. However, this action is dependent on the type of data I receive. In order to make this as flexible as possible, I'd like to start out in one class (which has a set of actions it can perform), then be able to rebless into another class should that set of actions be insufficient.

    I would caution you to do the following:

    • Make sure that the reblessings are in a known tree. In other words, if A doesn't work on X, it will always go to B, not C. If A doesn't work on Y, it goes to C, not B. Make sure that this set of transformations is well-documented.
    • Make sure that the set of transformations document above will cover all your possible needs.
    • Supply a default error case, which fails out with a useful error message.
    • Supply a way to follow the reblessings using some method of logging, even if it's only prints to the screen. Even after debugging the first iteration, leave the debugging in, just in case.
    • Make sure that all the classes use the same data structure hierarchy. In fact, I would build a base class, which supplies the attributes, and have the other classes supply the actions. The base class should have all the actions as virtual methods. (For more info, use Super Search or read Perl OO by Damian Conway.

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

      I concur; this is a very elegant solution, kind of like a dynamic dispatcher.

      Expanding upon the concept of making sure the reblessings are in a known tree, what you are describing appears to be a Finite State Machine.

      Given a current state (class A) and event (X fails), a new state (class B) is chosen; or, alternatively for a different event (Y fails), a different new state (class C) is selected.

      Construct a table with all possible states down the Y-axis and events across the X-axis, thus documenting the permitted state transitions. Here a dash (-) represents illegal states (i.e., should not happen, is an error if it does):

      state | event X | event Y | event Z -------+-----------+-----------+----------- A | B | C | E -------+-----------+-----------+----------- B | D | - | E -------+-----------+-----------+----------- C | E | - | - -------+-----------+-----------+----------- | | | ... etc. | | | -------+-----------+-----------+----------- E | - | - | B -------+-----------+-----------+-----------

      There are probably other ways to track a complex transition graph, but I've gotten great use out of this qapproach.

      dmm

      You can give a man a fish and feed him for a day ...
      Or, you can
      teach him to fish and feed him for a lifetime
(jeffa) Re: Replacing namespaces
by jeffa (Bishop) on Dec 11, 2001 at 00:19 UTC
    Interesting code you have there - i can't tell if it's a hack on the decorator pattern or maybe even the state pattern. At any rate, i think i see what the goal is:

    You instantiate the First class and test to see if it does the job, if it doesn't, revert to the Second class. This is cool, but it's a maintenance nightmare should you decide to add a Third, or a Fourth, ... all the way to the Nth possible class.

    I rewrote your code and added some tests - here is the solution domain:

    0-5: fail 6-9: second handles 10-: first handles
    And the code:
    use strict; use Data::Dumper; package First; sub new { my $class = shift; my $self = { foo => 'bar' }; return bless $self, $class; } sub test { my $self = shift; my $arg = shift || 10; return ($arg > 9) ? "okay" : bless $self, 'Second'; } package Second; sub new { my $class = shift; my $self = { foo => 'qux' }; return bless $self, $class; } sub test { my $self = shift; my $arg = shift || 4; return ($arg > 5) ? "okay" : undef; } package main; my $arg = shift || 10; my $t = First->new(); if (ref $t->test($arg)) { warn "first failed, trying second..."; die "second failed" unless $t->test($arg); } print (ref $t, " passed\n"); print Dumper $t;
    Try it out. Notice the very last expression which outputs the Dump of the object - notice how even though it might belong to package First or Second - $self is the same. That may or may not be a problem for you, because the technique to change $t from First to Second only changes it's package, not it's attributes.

    In conclusion, the only two problems i see are

    1. extensibility - need to generalize First, Second, Third, etc.
    2. not inheriting attributes from oringal class - but this might your 'feature' ...
    Three cheers for flexiblity!

    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)
    
Re: Replacing namespaces
by smferris (Beadle) on Dec 11, 2001 at 02:34 UTC

    Thanks for both of these responses! Both of them mention concerns that I've already thought about. None of them seem to be a problem.. Yet! 8) We'll see once I actually have the code soldered.

    My initial code sample is actually incorrect. It would've been more appropriatly written:

    #!/app/perl5.005/bin/perl package First; sub new { bless {}, 'First' }; sub test { my $self=shift; bless $self, 'Second'; $self->test; } 1; package Second; sub test { my $self=shift; print "Hello World!\n"; } 1; package main; my $t=new First(); print $t,"\n"; # Output: First=HASH(0x2864) $t->test or die $t->error; print $t,"\n"; # Output: Second=HASH(0xc2864)

    I'm shielding the caller from having to try the different approaches manually. (Notice the fail over moved into First::test from main::. Also added a test on the return of the main:: call to test.) The fail-over "rules" will be determined by the failure type. an ORA-00001 (Unique constraint violation) may execute a new class that scrubs the data, then re-tries the First class again. (Ok.. I'd probably just make that a conditional and "truncate" the table ;) or throw the row to an exceptions table)

    If the table to be loaded fails over to another approach, it will be logged and the configuration file can be updated to reflect which load process worked best. Bypassing First altogether when you know it's gonna fail.

    Again, thanks for the responses.. The two links mentioned are really cool! (Both of them sound fairly close to my situation) Now it's off to code!

    SMF 8)
      NB:
      sub new { my $class = shift; my $self = {}; bless $self, $class; return $self; }
      This allows for children classes to call you as a parent and have the inheritance work. You should NEVER hardcode anything. If you have to do something like bless {}, 'First';, at least do something like bless {}, __PACKAGE__;. That way, if you change the package name, you won't forget to change the name of the package you're blessing this thingy into.

      The reason to declare $self first is to allow for you to populate it. :-)

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

      Rather than hardcoding the fallback into the class itself, why not implement a dispatcher object, which, given the current class (available through caller()) and the kind of failure (?) does a lookup to determine the next class to try, to wit:

      my %FSM = ( First => { fail_a => 'Second', fail_b => 'Third', pass => undef }, # we're done Second =>{ fail_c => 'Third', pass => undef ) );

      Rather like a sparse-matrix.

      Then the code within any of the classes 'First', "Second' or 'Third' would merely consult the %FSM hash for what to do next. This brings up the problem of how (where?) to scope the %FSM hash. This should probably be a member within a common base-class:

      package Base; sub new { my $class = shift; my $self = { FSM => {First => { fail_a => 'Second', fail_b => 'Third', pass => undef }, # we're done Second =>{ fail_c => 'Third', pass => undef )} }; bless $self, $class; return $self; } package First; use base qw( Base ); sub new { # ... } package Second; use base qw( Base ); sub new { # ... } # ... and so on

      Any of the derived classes could access it as $self->{FSM}.

      dmm

      You can give a man a fish and feed him for a day ...
      Or, you can
      teach him to fish and feed him for a lifetime
(RhetTbull) Re: Replacing namespaces
by RhetTbull (Curate) on Dec 12, 2001 at 21:43 UTC
    I'm quite new at OO and OO Perl so I may not quite grasp the problem but this looks like a good use of TheDamian's NEXT module. It basically allows your object to dispatch a method to all other objects in the hierarchy. The following code shows an example of how this might be done.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://130753]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (4)
As of 2021-04-18 23:04 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?