in reply to Re: class constants in derived classes
in thread class constants in derived classes

beautiful - exactly what I want.

Spent most of the night reading the camel, the cookbook and the damian - finally found it in appendix A (p430) of the damian - package lexicals aren't visble, but a method returning them is - which means I need to defined a sub pattern {} in every derived class that redefines the class variable - which is a little messy.

This whole question came about as a result of refactoring an existing class hierarchy, and removing lots of duplicated code in each derived class. And having made it all clean, now every class that redefines the pattern neeeds a method, thats exactly the same as the base and other derived class methods, that returns the pattern.

Maybe I need a deep think. I dont want to use constructors in the derived class', and call SUPER::new, then put pattern into the blessed hash. Maybe the derived class' that redefine the pattern can have a sub _redefine_pattern, that the constructor in the base class calls - we should check that the derived class has the method first via an ->can() call?

Or should I add @EXPORT = ($PATTERN); to the derived modules (evil in an OO module ;-) )

  • Comment on Re: Re: class constants in derived classes

Replies are listed 'Best First'.
Re^3: class constants in derived classes
by adrianh (Chancellor) on Apr 04, 2003 at 08:49 UTC
    This whole question came about as a result of refactoring an existing class hierarchy, and removing lots of duplicated code in each derived class. And having made it all clean, now every class that redefines the pattern neeeds a method, thats exactly the same as the base and other derived class methods, that returns the pattern.

    I have been a drinker of the OO Kool Aid for many years - but since these methods capture the difference between the classes it seems like an elegent solution to me ;-) Certainly more elegant than exporting a global. To-may-to, To-mah-to. I guess.

    If only some sub-classes redefine pattern just have the base class's pattern method return the default instead of throwing an exception. If you don't want to change the default, don't write a pattern method in the sub-class. I'm not quite sure from your description why you end up with more code?

      OK, now I have to get into details.

      The client has 17 divisions, and these 17 divisions are basically divided into 6 account-receivable (A/R) systems (they went on buying spree in the past, and have never integrated to a central corporate A/R system).

      The client sends a text file every month for each division, containing the customer statements for that month. There is one file per division, each containing several thousand customer statements.

      The code I wrote munges these files to a print-ready format, laser prints them, folds and inserts the statement into an envelope, and lodges them with the post office (our company produces about 50M print pages a month)

      My design for file processing is to treat the files as statement 'containers' (as in STL containers), and I have some objects that basically try to emulate a forward iterator on these 'containers'. The 'value' _pointed_ to by each iterator is a customer statement.

      I have a base class PB_File, (named after the client), that has methods that iterate for 'most' of the files. The object has 'public' methods new, get_record and next.

      In the PB_File class, the methods try to find the start and end lines of a customer statement by searching for lines that match START_PATTERN and END_PATTERN regex's.

      PB_File's get_record code does something like this :-

      if ($class->_is_start_pattern($line)) { # we have found the start of a customer record ... } sub _is_start_pattern { my ($class, $line) = @_; return ($line =~ $class->START_PATTERN); # I really wish we could do #return ($line =~ $class::START_PATTERN); } ...

      Each A/R system subclass's PB_File to provide START_PATTERN and END_PATTERN regex's, and so, each subclass must also provide a method START_PATTERN to return that regex. This is the almost identical code that each subclass' ,that has a different pattern, is forced to repeat.

      sub START_PATTERN { return qr(^(\d)\s+\1$); }

      One of the A/R systems produces a statement file quite different to the others, so I have a class JBA_File (named after the A/R system), that @ISA(PB_File), and that overloads the get_record and next methods (new is unchanged). There are 3 division handled by the JBA A/R system, and, in yet another cunning move by the client, each division has a different VERSION of the JBA A/R system running - producing almost, but not quite, identical statement formats. All have the same start pattern, so that can go in the START_PATTERN method in the JBA_File class (which now also requires a sub START_PATTERN method).

      Now each of the three divisions end in slightly different ways.

      One has each statement end in exactly the same way - hence it has an overloaded END_PATTERN_1 method of

      sub END_PATTERN_1 { return qr(^<pattern for this division>$); }

      The other two _can_ end like that, but they both also have an alternate ending pattern to statements (different for each), so they each have sub END_PATTERN_2 methods, and that is why get_record and next are overloaded in JBA_File - to handle the alternate ending scenario's. JBA_File's get_record method hence looks like this :-

      if ($class->_is_end_pattern_1($line) || $class->_is_end_pattern_2($line)) { ... }

      So I am unhappy about the fact that I have to define a sub START_PATTERN {} and sub END_PATTERN {} for every subclass of PB_File that has a new pattern. I would rather the subclasses had no methods (as they don't DO anything different), but just had public class attributes that were the patterns. If perl was able to handle C++'s static class attributes (or Java's public static class constants), I could just defined a $START_PATTERN in each class that redefined a pattern and have $class::START_PATTERN resolve to the correct class. But, then again, if that is the biggest hassle I have with perl class's, I dont have much to complain about.

      I am sure your head is starting to spin at all this (mine was), but it all basically works - for now.

        You said:

        sub _is_start_pattern { my ($class, $line) = @_; return ($line =~ $class->START_PATTERN); # I really wish we could do #return ($line =~ $class::START_PATTERN); }

        Well, if you really want to you can always do something like this:

        { package PB_File; our $START_PATTERN = "base-pattern"; sub pattern { my $class = shift; { no strict 'refs'; ${"${class}::START_PATTERN"}; }; } } { package An_AR; use base qw(PB_File); our $START_PATTERN = "sub-pattern"; }; use Test::More tests => 2; is( PB_File->pattern, 'base-pattern' ); is( An_AR->pattern, 'sub-pattern' );

        However, for me, you're not repeating any more information by having a method call. Compare:

        our $START_PATTERN = qr(^(\d)\s+\1$); sub START_PATTERN { qr(^(\d)\s+\1$) };

        Okay - you have to type one more character :-)

        You also have the advantage that, if you use methods, any future code maintainer doesn't have to learn (or remember) that some package globals are "special".