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

This question is for those with a good understanding of object-oriented design in Perl. The question I have is, under the normal way of using my program, the hierarchy below is fine --- objects of Y have their own B() method but use the A() method from the base class:
package X; sub A { " base class implementation" } sub B { " base class implementation" } 1; package Y; @ISA (X); # Let's use A() from the base class sub B { "local implementation" }

However, based on a command line option, it is sometimes necessary for objects of Y to do something special instead of X::A(). I think I just arrived at the solution to my problem.

What I should do is this:

sub Y::A { unless ($getopt{special_case}) { $self->SUPER::A(); return; } # do the stuff based on the command line arg if # the special case was chosen from the cmd line. }

I was breaking out in a sweat here, thinking I was going to have to add subroutines to package Y dynamically at runtime based on cmdline args. Phew.

Replies are listed 'Best First'.
Re: Best Method of Object-Oriented Behavior Addition?
by merlyn (Sage) on Sep 29, 2000 at 17:36 UTC
    You could also just create class Z, subclassing Y, which has a Z::A with your special stuff. Then create your objects of Z instead of Y on the condition.
    BEGIN { package X; sub A {} sub B {} } BEGIN { package Y; @ISA=qw(X); sub B {} } BEGIN { package Z; @ISA=qw(Y); sub A {} } my $object = ($opt{specialcase} ? 'Z' : 'Y')->new(@newargs);

    -- Randal L. Schwartz, Perl hacker


    update: I added the BEGIN above. Too asleep when I hacked before to remember that. {grin}
      Excellent solution to the problem.

      Another one could be to use the "decorator" design pattern instead of subclassing. Here is an interesting discussion of when and why to use this pattern:

      http://www.stevenblack.com/Articles/PTN-Decorator.asp

      (it's not Perl, but you get the idea)

      From your description I would go with the subclass solution, but it's always good to know your options.

      /J

Re: Best Method of Object-Oriented Behavior Addition?
by japhy (Canon) on Sep 29, 2000 at 18:47 UTC
    Hadn't someone (Abigail, perhaps) brought up the notion of per-object methods? That, is something not unlike:
    package GenObj; my %methods = ( name => sub { $_[0]{DATA}{name} }, setname => sub { $_[0]{DATA}{name} = $_[1] }, # ... ); sub new { my ($class) = @_; bless { DATA => {}, METHODS => {%methods} }, $class; } AUTOLOAD { (my $method = $AUTOLOAD) =~ s/.*:://; $_[0]{METHODS}{$method} ? goto &{$_[0]{METHODS}{$method}} : die "unsupported method $method for object"; } package main; $foo = GenObj->new; $foo->setname("Jeff"); print $foo->name;
    Update
    To stop the repeated calls to the AUTOLOAD method, I think it would be pragmatic to set each object up in its own sub-class of the main class. Then, after the first call to a method has been made, the methods are created as functions in the sub-class. Perhaps we could do away with the hash of default methods altogether. Take this example:
    $x = new GenObj; # ref($x) eq 'GenObj::a' $y = new GenObj -foo; # ref($y) eq 'GenObj::b' $x->get_foo; # calls $x->GenObj::get_foo $y->get_foo; # calls $y->GenObj::b::get_foo
    Here's my sample implementation:
    package GenObj; # the following methods can be generated # automatically with one of the Class:: # modules... # these are the EXPECTED inherited methods # of GenObj objects sub set_name { $_[0]{name} = $_[1] } sub get_name { $_[0]{name} } sub set_age { $_[0]{age} = $_[1] } sub get_age { $_[0]{age} } my $CHILD = 'a'; sub new { no strict 'refs'; # naughty things transpire my $class = shift; my $obj = bless {}, "${class}::$CHILD"; @{"${class}::${CHILD}::ISA"} = ($class); for (map s/^-//, @_) { *{"${class}::${CHILD}::get_$_"} = \&{"${class}::Sub::get_$_"}; *{"${class}::${CHILD}::set_$_"} = \&{"${class}::Sub::set_$_"}; } return $obj; ); package GenObj::Sub; # these are the specialized methods # to be inherited on demand sub set_name { $_[0]{name} = condense($_[1]) } sub get_name { expand($_[0]{name}) } sub set_age { $_[0]{age} = age2sec($_[1]) } sub get_age { sec2age($_[0]{age}) } # these are some utility functions # as called above sub condense; sub expand; sub age2sec; sub sec2age; 1;
    I have not yet tested this code, but its use is something like:
    use GenObj; my $normal = new GenObj; my $diffage = new GenObj -age; my $diffname = new GenObj -name; my $diffboth = new GenObj -age, -name; $normal->set_age(10); # GenObj::set_age $normal->get_name; # GenObj::get_name $diffage->set_age(10); # GenObj::Sub::set_age $diffage->get_name; # GenObj::get_name $diffname->set_age(10); # GenObj::set_age $diffname->get_name; # GenObj::Sub::get_name $diffboth->set_age(10); # GenObj::Sub::set_age $diffboth->get_name; # GenObj::Sub::get_name


    $_="goto+F.print+chop;\n=yhpaj";F1:eval
Re: Best Method of Object-Oriented Behavior Addition?
by Fastolfe (Vicar) on Sep 29, 2000 at 19:58 UTC
    Be sure that you're returning the right thing from your pseudo-superceded method:
    return $self->SUPER::A();
    and not:
    $self->SUPER::A(); return;
    The latter will always cause your method to appear to fail, or not return anything, even if SUPER::A is meant to return something legitimate once in a while.
Re: Best Method of Object-Oriented Behavior Addition?
by jreades (Friar) on Sep 29, 2000 at 19:25 UTC

    Is the special case exclusive? Meaning, is it a case the all objects of class 'Y' will behave differently based on the command-line switch? Or is it just once in a while?

    Based on the answer to that you have two choices:

    • Intermittent behaviour: your solution is an excellent one (extending won't work)
    • Consistent behaviour: I'd change the constructor so that the method itself works differently

    my $0.02

Re: Best Method of Object-Oriented Behavior Addition?
by AgentM (Curate) on Sep 29, 2000 at 18:49 UTC
    hmmm...will perl6 have support virtual type classes and functions? I haven't sifted through all of the RFCs yet.
    AgentM Systems or Nasca Enterprises is not responsible for the comments made by AgentM- anywhere.
      In Perl, if you have class X with methods 'foo' and 'bar', and class Y which inherits from X, but has a method 'foo', then the following takes place:
      package X; sub foo; sub bar; package Y; @ISA = 'X'; sub foo; package main; X->foo; # calls X::foo('X'); X->bar; # calls X::bar('X'); Y->foo; # calls Y::foo('Y'); Y->bar; # calls X::bar('Y');
      In C++, you would have to declare X::foo() to be virtual:
      // yes, this is C++, so shoot me class X { public: virtual int foo () { return 1; } int bar () { return 2; } }; class Y : public X { virtual int foo () { return 3; } };
      You don't need to do that in Perl, since an object's method search tree starts in the object's class (Y, in this case).

      Since an object in Y inherits from X, then Y->isa(X) is true, just like you can write a function in C++ like:
      void printStuff (X& obj);
      and it will accept an object of class Y (because of the rules of inheritance). In Perl, you can do this checking via the isa() method shown above.

      As for ensuring an abstract base class never has objects of IT specifically -- only of it's sub-classes -- you can't stop a person from brute-force bless($obj,'Employee'), but you can make Employee::new die if its first argument is 'Employee':
      package Employee; sub new { my ($class,$fname,$lname) = @_; die if $class eq 'Employee'; bless { FNAME => $fname, LNAME => $lname }, $class; } package Employee::Boss; @ISA = qw( Employee ); sub new { my ($class,$fname,$lname) = @_; my $self = $class->SUPER::new($fname,$lname); $self->{PAY} = 1_000_000; bless $self, $class; } # and so on...
      And then a program would run like:
      use Employee; # defines Employee, Employee::Boss, # Employee::Hourly, and Employee::Intern $joe = Employee::Intern->new('Joe', 'Schmoe'); $jay = Employee::Boss->new('Jay','Schmay'); $not = Employee->new('Not', 'Happening'); # <-- dies


      $_="goto+F.print+chop;\n=yhpaj";F1:eval