Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

replace conditionals with polymorphism

by Anonymous Monk
on Feb 09, 2009 at 16:11 UTC ( #742474=perlquestion: print w/ replies, xml ) Need Help??
Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I don't get how the topic applies to my Perl program. It is basically one big switch.

$foo = indirect user input; given ($foo) { when (A) { a; } when (B) { b; } when (C) { b; one additional statement; } when (D) { d; } default { similar_to_a; } }

Following the advice in the video, I converted the different blocks into subroutines with the same name under differently named subclasses.

However, I have no idea what to write in the base class. There are no examples. I expected something on the design patterns wiki under the lemma ConditionalElimination at slowass.net, but it's down.

First, I need an abstract method. Is it just sub foo {}; or sub foo; or what? Or should I leave the declaration away altogether?

Second, but most important, now that the switch is gone, how do I go about from user input $foo to a certain subclass foo method? Drawing a blank here.

Third, sharing code between B and C is more difficult. Formerly, when everything was in one package, I just had a sub for the common code. How do I handle that now that they are in different classes?

I'm not convinced that once I solve the problems, the conversion is going to be a big win, but I want to learn it anyway for the future. Please answer accordingly.

Comment on replace conditionals with polymorphism
Select or Download Code
Re: replace conditionals with polymorphism
by revdiablo (Prior) on Feb 09, 2009 at 16:55 UTC

    This may be a good exercise, but I'm not so convinced it's the best thing to do in practice. You now have 5 classes, each with a single method? That seems a little contrived. I would stick with the switch statement.

    Update: I guess you said the same thing later in your post; I should have read it more carefully. I'd still like to reinforce your feeling with my own. I wouldn't bother solving all the problems you listed.

Re: replace conditionals with polymorphism
by Corion (Pope) on Feb 09, 2009 at 17:00 UTC

    Personally, I'd just use a dispatch table, at least as long as your conditions A, B, C and D conveniently map to strings, like plain strings or regular expressions do:

    my $foo = get_user_input(); my %actions = ( A => sub {a;}, B => sub {b;}, C => sub {c; b;}, D => sub {d;}, ); if (not exists $actions{$foo}) { $foo = 'A'; }; $actions{$foo}->();
Re: replace conditionals with polymorphism
by juster (Friar) on Feb 09, 2009 at 17:20 UTC

    I agree this is a bad example to use polymorphism in but you might as well try it.

    First, I need an abstract method. Is it just sub foo {}; or sub foo; or what? Or should I leave the declaration away altogether?

    I usually croak in abstract methods, in hopes they are always overloaded.

    sub process_input { croak "Cannot use an abstract method; }
    Second, but most important, now that the switch is gone, how do I go about from user input $foo to a certain subclass foo method? Drawing a blank here.

    You can maybe use a Factory design pattern, or have a sub choose the object. This is basically another switch statement :P. Or maybe you can use a dispatch table like the one shown above.

    Third, sharing code between B and C is more difficult. Formerly, when everything was in one package, I just had a sub for the common code. How do I handle that now that they are in different classes?

    Hmmm.. you could have C be a subclass of B...

Re: replace conditionals with polymorphism
by gnosti (Friar) on Feb 09, 2009 at 17:44 UTC
    Here is a simple example. I had been using conditionals in a project to be able to run the same code in console mode, or with a GUI output. I replaced these conditionals with lines like this:

    $ui->refresh

    In the Text package, refresh is just a stub, i.e. refresh {}, while the Graphical package has a functioning refresh routine. (In general, the stubs might better be in the base class.) I create the object in the first lines of the program based on a command-line switch.

    The program is a single file. I share data by using our ($var1, @var2, %var3) enabling all packages to access these variables without a package prefix.

    This is how I got the polymorphism to work, thanks to the advice of some helpful monks.

    use strict; use warnings; package UI; our @ISA = (); sub new { my $class = shift; return bless {@_}, $class } sub hello {print "superclass hello\n"}; package UI::Graphical; our @ISA = 'UI'; sub hello {print "make a window\n";} package UI::Text; our @ISA = 'UI'; sub hello {print "hello world!\n";} my $ui = UI->new; $ui->hello; my $tui = UI::Text->new; $tui->hello; my $gui = UI::Graphical->new; $gui->hello;

    UPDATE: Changed @ISA = '' to </c> @ISA = ()</c> as cautioned by chromatic.

      package UI; our @ISA = '';

      Careful; you've just inherited from a class with no name.

        You likely know this, but others mightn't:

        Perl provides the class with no name. It's identical to the main:: namespace:

        package Hello; @Hello::ISA=''; package main; sub p { print for @_ }; Hello->p('World')
Re: replace conditionals with polymorphism
by ELISHEVA (Prior) on Feb 09, 2009 at 21:01 UTC

    IMHO you are having trouble applying the video because he isn't really talking about isolated switch statements. In the case of an isolated switch statement, OO merely replaces one sort of switch statement with another. The only way you can get rid of the switch is to replace it with a object belonging to a dynamically chosen class. But how are you going to select the class? Of course with another switch statement! (or other conditional logic)

    Generally replacement of switch statements with classes only buys you something if you have a similar switch statement in more than one place. This can happen if

    • (A) one kind of behavior is conditional on the type but that type dependent behavior is used in more than one place. If the behavior depends on some state data then encapsulating the type-specific behavior and the state data it depends on in an object is a good idea. If there is no per-object state data, then Corion's suggestion of a dispatch table is usually preferable -- with one caveat. Requirements can change over time and a problem that looks like it has no state data today may indeed have state data tomorrow.
    • (B) more than one type of behavior is conditional on the type of $foo.

    In either of these cases OO lets you reuse the decision that lead to class selection over and over without having to make the actual decision over and over.

    In the video his first example (nodes of a parse tree) is of type A - repeated use of the same switch statement. Even though you need a switch statement to assign each node to a class, once you have done so, you can act as if each node in the tree is essentially the same object and avoid messy switch statements whenever you visit a node. All you have to do is call its evaluate(...) method.

    The second example in the video - the Update class - is an example of type B - more than one action dependent on type. In the case of the Update class we have the execute(...) and render(...) methods.

    That being said, this is what I would do to convert your code:

    1. create a base class with a single method, lets say "frobnicate"
    2. implement the "frobnicate" method of the base class with your default case of the switch statement
    3. for each non-default case in the switch statement, create a subclass
    4. implement the frobnicate method of the subclass with the statements belonging to each non-default case.

    To replace the switch statement, I would use three lines:

    my $sClass = derive_class($foo); my $oWhatWasOnceASwitchStatementCase = $sClass->new(); $oWhatWasOnceASwitchStatementCase->frobnicate();

    The full code looks something like this. To keep it short I've reduced the number of special cases from A,B,C,D to just A,B. I've also set "$foo" from the command line so you can experiment a bit with how different values of "$foo" might behave.

    Best, beth

    Update: various edits to improve clarity.

      The only way you can get rid of the switch is to replace it with a object belonging to a dynamically chosen class. But how are you going to select the class? Of course with another switch statement (or other conditional logic)!
      Or with a naming convention, right? But then you can do that with a dispatch table as well.

      I think that the main thing that OOP polymorphism gains you is that it's a kind of plug-in architecture -- you can add handling for new cases by adding a new module, without changing the existing ones.

        A dynamically built dispatch table also offers the ability to add handling for new cases without changing the existing ones. And it has the benefits that it is simpler to do, and doesn't push you to spread your logic across multiple files.

        As ELISHEVA said, the big win with OO is that it gives you a framework to dispatch multiple related decisions.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others chanting in the Monastery: (7)
As of 2014-08-30 12:28 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The best computer themed movie is:











    Results (293 votes), past polls