http://qs321.pair.com?node_id=176232


in reply to Where/When is OO useful?

Excellent post. I would add the guideline

"Create a class when you want a resusable perl module with a flexible interface"

Many OO CPAN modules are simplicity themselves to use and modify.

-Mark

Replies are listed 'Best First'.
Re: Where/When is OO useful?
by Abigail-II (Bishop) on Jun 21, 2002 at 12:59 UTC
    Many OO CPAN modules are simplicity themselves to use and modify.

    I don't agree. I think many OO modules are hard to modify - the latter meaning "subclass".

    Here's coding problem.

    Someone has written a BaseballPlayer class, to maintain statistics of players. Your task is to subclass it to create a BaseballPlayer::Pitcher class so the additional statistics for pitchers can be dealt with.

    Where are you going to store your instance data?

    If you answer "in the hash returned by the constructor of the BaseballPlayer class", or "in whatever object the superclass uses", or "that depends on how the BaseballPlayer class is implemented", you fail the test. And why do you fail? Because you are breaking the basic concept of object orientness: encapsulation. It shouldn't matter how the superclass is implemented. Unfortunally, Perl doesn't make it easy to use inheritance. It all works handy-dandy if you can enforce a certain style, implementation or module usage of the total inheritance tree, but that isn't usually the case. It wouldn't be code reuse if you had to write everything yourself, now would it?

    Abigail

      Where are you going to store your instance data?

      Since you asked... in a variable outside the object itself: a closure, in all likelihood. In my experience, this is the best use for flyweight objects in Perl, far better than as an awkward method of strong encapsulation. Here is how I'd do it:

      #!/usr/bin/perl use strict; use warnings; package BaseballPlayer; use Carp; my %attrib; sub BEGIN { %attrib = map { $_ => 1 } qw( RBI Batting_Average Hits Runs Stolen_Bases Games_Played ); no strict 'refs'; for my $n ( keys %attrib ) { *$n = sub { $_[0]->{$n} } } } sub new { my( $class, %arg ) = @_; exists $attrib{$_} or croak "Unknown stat: $_" for keys %arg; $arg{$_} ||= 0 for keys %attrib; bless \%arg, $class; } package BaseballPlayer::Pitcher; our @ISA = 'BaseballPlayer'; { my %object; sub new { my( $class, %arg ) = @_; my %pitcher_stat = map { $_ => delete $arg{$_} || 0 } qw( ERA Stri +keouts ); my $base = BaseballPlayer->new(%arg); my $ret = bless $base, $class; $object{$ret} = \%pitcher_stat; $ret; } sub ERA { $object{$_[0]}{ERA} } sub Strikeouts { $object{$_[0]}{Strikeouts} } sub DESTROY { delete $object{$_[0]} } } package main; my $p = BaseballPlayer::Pitcher->new( Hits => 23, ERA => 4.32 ); print $p->Hits, "\n"; print $p->ERA, "\n";

      Likely you have your own solution; if it is significantly different (or especially if it's better) than mine, do share it. This solution passes the tests you mentioned, and has become a regular habit for me. I agree with your point: Perl doesn't make it easy to use inheritance. This is a wordy, tiresome ritual, and thus is error-prone. Various aspects of Perl's OO require such rituals, however; personally, I wouldn't single out inheritance on this account.

      Update: Changed $p's ERA to something realistic, upon zdog's advice.

      Update: Simplified my code, upon tye's advice. My inclusion of needless code was a cargo-cult imitation of my own practices, which reflected the needs of prior projects. This, I think, underscores my point about the unfortunate effects of rituals which compensate for the shortcomings of a language.

        I go one step further than "fly weight objects". I take a lexical hash for each attribute, and index those with the object. This means you never have to use string literals to access attributes (and hence you get the full benefit of using strict), and you only need one hash query instead of two to access the attribute.

        Here's my implementation of the BaseballPlayer::Pitcher class. It's totally independent of the implementation of the BaseballPlayer class (although it could be that you want to mask the constructor - but that's ok because the constructor is part of the API). I call this technique "Inside Out Objects" (people who saw my presentation at YAPC know all about it).

        package BaseballPlayer::Pitcher; { use vars '@ISA'; @ISA = 'BaseballPlayer'; my (%ERA, %Strikeouts); sub ERA : lvalue {$ERA {+shift}} sub Strikeouts : lvalue {$Strikeouts {+shift}} sub DESTROY { my $self = shift; delete $ERA {$self}, $Strikeouts {$self} } }

        Abigail

      The truth is that ineritance sucks. There are problems where inheritance is a good solution, but most of the time composition is a better method, and not just because it promotes encapsulation. A has-a relationship is just plain easier to code with. Even if your language provides a good mechanism for inheritance (which Perl does not) if your writing a substantial class than you'll find yourself looking under the hood 9 times out of 10.

      Cheers,
      Erik

      Light a man a fire, he's warm for a day. Catch a man on fire, and he's warm for the rest of his life. - Terry Pratchet