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

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.

Replies are listed 'Best First'.
Re^5: class constants in derived classes
by adrianh (Chancellor) on Apr 07, 2003 at 09:42 UTC

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